Datasets and DataLoaders¶

In [172]:
# Instalacion de las librerias necesarias
%pip install transformers
%pip install torch torchvision torchaudio
%pip install opencv-python
%pip install numpy
%pip install albumentations
%pip install pillow
%pip install matplotlib
%pip install pandas

%pip install seaborn
%pip install tqdm

%pip install segmentation_models_pytorch

#%pip install sklearn
%pip install scikit-learn
Requirement already satisfied: transformers in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (4.57.3)
Requirement already satisfied: filelock in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (3.20.0)
Requirement already satisfied: huggingface-hub<1.0,>=0.34.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (0.36.0)
Requirement already satisfied: numpy>=1.17 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (2.2.6)
Requirement already satisfied: packaging>=20.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (25.0)
Requirement already satisfied: pyyaml>=5.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (6.0.3)
Requirement already satisfied: regex!=2019.12.17 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (2025.11.3)
Requirement already satisfied: requests in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (2.32.5)
Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (0.22.1)
Requirement already satisfied: safetensors>=0.4.3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (0.7.0)
Requirement already satisfied: tqdm>=4.27 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from transformers) (4.67.1)
Requirement already satisfied: fsspec>=2023.5.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (2025.12.0)
Requirement already satisfied: typing-extensions>=3.7.4.3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub<1.0,>=0.34.0->transformers) (4.15.0)
Requirement already satisfied: colorama in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from tqdm>=4.27->transformers) (0.4.6)
Requirement already satisfied: charset_normalizer<4,>=2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->transformers) (3.4.4)
Requirement already satisfied: idna<4,>=2.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->transformers) (3.11)
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->transformers) (2.6.1)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->transformers) (2025.11.12)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: torch in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.9.1)
Requirement already satisfied: torchvision in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (0.24.1)
Requirement already satisfied: torchaudio in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.9.1)
Requirement already satisfied: filelock in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (3.20.0)
Requirement already satisfied: typing-extensions>=4.10.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (4.15.0)
Requirement already satisfied: sympy>=1.13.3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (1.14.0)
Requirement already satisfied: networkx>=2.5.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (3.6.1)
Requirement already satisfied: jinja2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (3.1.6)
Requirement already satisfied: fsspec>=0.8.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (2025.12.0)
Requirement already satisfied: setuptools in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (80.9.0)
Requirement already satisfied: numpy in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torchvision) (2.2.6)
Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torchvision) (12.0.0)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from sympy>=1.13.3->torch) (1.3.0)
Requirement already satisfied: MarkupSafe>=2.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from jinja2->torch) (3.0.3)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: opencv-python in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (4.12.0.88)
Requirement already satisfied: numpy<2.3.0,>=2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from opencv-python) (2.2.6)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: numpy in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.2.6)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: albumentations in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.0.8)
Requirement already satisfied: numpy>=1.24.4 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (2.2.6)
Requirement already satisfied: scipy>=1.10.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (1.16.3)
Requirement already satisfied: PyYAML in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (6.0.3)
Requirement already satisfied: pydantic>=2.9.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (2.12.5)
Requirement already satisfied: albucore==0.0.24 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (0.0.24)
Requirement already satisfied: opencv-python-headless>=4.9.0.80 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (4.12.0.88)
Requirement already satisfied: stringzilla>=3.10.4 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albucore==0.0.24->albumentations) (4.4.0)
Requirement already satisfied: simsimd>=5.9.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albucore==0.0.24->albumentations) (6.5.3)
Requirement already satisfied: annotated-types>=0.6.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (0.7.0)
Requirement already satisfied: pydantic-core==2.41.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (2.41.5)
Requirement already satisfied: typing-extensions>=4.14.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (4.15.0)
Requirement already satisfied: typing-inspection>=0.4.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (0.4.2)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: pillow in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (12.0.0)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: matplotlib in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (3.10.8)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (1.3.3)
Requirement already satisfied: cycler>=0.10 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (4.61.0)
Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (1.4.9)
Requirement already satisfied: numpy>=1.23 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (2.2.6)
Requirement already satisfied: packaging>=20.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (25.0)
Requirement already satisfied: pillow>=8 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (12.0.0)
Requirement already satisfied: pyparsing>=3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (3.2.5)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: six>=1.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: pandas in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.3.3)
Requirement already satisfied: numpy>=1.26.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pandas) (2.2.6)
Requirement already satisfied: python-dateutil>=2.8.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pandas) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pandas) (2025.2)
Requirement already satisfied: six>=1.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: seaborn in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (0.13.2)
Requirement already satisfied: numpy!=1.24.0,>=1.20 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from seaborn) (2.2.6)
Requirement already satisfied: pandas>=1.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from seaborn) (2.3.3)
Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from seaborn) (3.10.8)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.3)
Requirement already satisfied: cycler>=0.10 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.61.0)
Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.9)
Requirement already satisfied: packaging>=20.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0)
Requirement already satisfied: pillow>=8 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (12.0.0)
Requirement already satisfied: pyparsing>=3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.5)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pandas>=1.2->seaborn) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pandas>=1.2->seaborn) (2025.2)
Requirement already satisfied: six>=1.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: tqdm in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (4.67.1)
Requirement already satisfied: colorama in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from tqdm) (0.4.6)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: segmentation_models_pytorch in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (0.5.0)
Requirement already satisfied: huggingface-hub>=0.24 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (0.36.0)
Requirement already satisfied: numpy>=1.19.3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (2.2.6)
Requirement already satisfied: pillow>=8 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (12.0.0)
Requirement already satisfied: safetensors>=0.3.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (0.7.0)
Requirement already satisfied: timm>=0.9 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (1.0.22)
Requirement already satisfied: torch>=1.8 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (2.9.1)
Requirement already satisfied: torchvision>=0.9 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (0.24.1)
Requirement already satisfied: tqdm>=4.42.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from segmentation_models_pytorch) (4.67.1)
Requirement already satisfied: filelock in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub>=0.24->segmentation_models_pytorch) (3.20.0)
Requirement already satisfied: fsspec>=2023.5.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub>=0.24->segmentation_models_pytorch) (2025.12.0)
Requirement already satisfied: packaging>=20.9 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub>=0.24->segmentation_models_pytorch) (25.0)
Requirement already satisfied: pyyaml>=5.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub>=0.24->segmentation_models_pytorch) (6.0.3)
Requirement already satisfied: requests in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub>=0.24->segmentation_models_pytorch) (2.32.5)
Requirement already satisfied: typing-extensions>=3.7.4.3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from huggingface-hub>=0.24->segmentation_models_pytorch) (4.15.0)
Requirement already satisfied: sympy>=1.13.3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch>=1.8->segmentation_models_pytorch) (1.14.0)
Requirement already satisfied: networkx>=2.5.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch>=1.8->segmentation_models_pytorch) (3.6.1)
Requirement already satisfied: jinja2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch>=1.8->segmentation_models_pytorch) (3.1.6)
Requirement already satisfied: setuptools in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch>=1.8->segmentation_models_pytorch) (80.9.0)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from sympy>=1.13.3->torch>=1.8->segmentation_models_pytorch) (1.3.0)
Requirement already satisfied: colorama in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from tqdm>=4.42.1->segmentation_models_pytorch) (0.4.6)
Requirement already satisfied: MarkupSafe>=2.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from jinja2->torch>=1.8->segmentation_models_pytorch) (3.0.3)
Requirement already satisfied: charset_normalizer<4,>=2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->huggingface-hub>=0.24->segmentation_models_pytorch) (3.4.4)
Requirement already satisfied: idna<4,>=2.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->huggingface-hub>=0.24->segmentation_models_pytorch) (3.11)
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->huggingface-hub>=0.24->segmentation_models_pytorch) (2.6.1)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from requests->huggingface-hub>=0.24->segmentation_models_pytorch) (2025.11.12)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: scikit-learn in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (1.8.0)
Requirement already satisfied: numpy>=1.24.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-learn) (2.2.6)
Requirement already satisfied: scipy>=1.10.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-learn) (1.16.3)
Requirement already satisfied: joblib>=1.3.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-learn) (1.5.2)
Requirement already satisfied: threadpoolctl>=3.2.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-learn) (3.6.0)
Note: you may need to restart the kernel to use updated packages.
In [173]:
# impotacion de librerias
import json, os, torch, cv2, numpy as np, albumentations as A
from PIL import Image; from matplotlib import pyplot as plt
from glob import glob; from PIL import ImageFile
from torch.utils.data import random_split, Dataset, DataLoader
from albumentations.pytorch import ToTensorV2
import random

import sklearn.metrics as metrics
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.metrics import confusion_matrix, roc_auc_score, mean_squared_error, r2_score


import os
import numpy as np

import pandas as pd

import albumentations as A
from albumentations.pytorch import ToTensorV2

ImageFile.LOAD_TRUNCATED_IMAGES = True


import segmentation_models_pytorch as smp, time
from tqdm import tqdm
from torch.nn import functional as F


import cv2
import matplotlib.pyplot as plt
import torch
import numpy as np
from PIL import Image
from typing import List, Tuple, Optional
import matplotlib.patches as mpatches

import random
import matplotlib.pyplot as plt
import math 
from torchvision import transforms as tfs

import torch
import torch.nn as nn
import time
In [174]:
# Exploracion del archivo CSV con los labels

path_csv = "C:\\Users\\pc\\Desktop\\Proyecto_Segmentacion\\dataset\\labels.csv"   # ajusta a tu ruta real

df = pd.read_csv(path_csv)


# #limpieza de los nombres de las columnas
# df.columns = df.columns.str.strip().str.replace('\ufeff', '')

# print(df.head())
# print(df.tail())
# print(df.shape)

# col = "label_list"   # nombre correcto
In [175]:
# Copia de seguridad
df2 = df.copy()

df2 = pd.read_csv(path_csv)

#limpieza de los nombres de las columnas
df2.columns = df2.columns.str.strip().str.replace('\ufeff', '')

print(df2.head())
print(df2.tail())
print(df2.shape)
   Unnamed: 0   label_list
0           0   background
1           1  accessories
2           2          bag
3           3         belt
4           4       blazer
    Unnamed: 0 label_list
54          54        top
55          55       vest
56          56     wallet
57          57      watch
58          58     wedges
(59, 2)
In [176]:
col = "label_list"   # nombre correcto


# Mapeo SEMÁNTICO (texto → clase limpia)
mapping = {
    # --- TOPWEAR (tops + outerwear juntos) ---
    "blazer": "topwear",
    "jacket": "topwear",
    "coat": "topwear",
    "cape": "topwear",
    "cardigan": "topwear",
    "suit": "topwear",

    "t-shirt": "topwear",
    "shirt": "topwear",
    "top": "topwear",
    "sweater": "topwear",
    "hoodie": "topwear",
    "blouse": "topwear",
    "bodysuit": "topwear",
    "sweatshirt": "topwear",
    "vest": "topwear",

    # --- LOWERWEAR ---
    "pants": "lowerwear",
    "jeans": "lowerwear",
    "leggings": "lowerwear",
    "skirt": "lowerwear",
    "shorts": "lowerwear",
    "panties": "lowerwear",
    "tights": "lowerwear",

    # --- DRESSES ---
    "dress": "dress",
    "romper": "dress",

    # --- FOOTWEAR ---
    "boots": "footwear",
    "sneakers": "footwear",
    "flats": "footwear",
    "heels": "footwear",
    "loafers": "footwear",
    "sandals": "footwear",
    "wedges": "footwear",
    "pumps": "footwear",
    "clogs": "footwear",
    "shoes": "footwear",

    # --- BODY (reabierto) ---
    "hair": "body",
    "skin": "body",

    # --- BACKGROUND ---
    "background": "background"
}

# Aplicar mapeo (todo lo no definido → ignore)
df2["clean_label"] = df2["label_list"].map(mapping).fillna("ignore")

# Mapeo NUMÉRICO FINAL (coherente con clean_label)
numeric_mapping = {
    "background": 0,
    "topwear": 1,
    "lowerwear": 2,
    "dress": 3,
    "footwear": 4,
    "body": 5,
    "ignore": 255
}

df2["numeric_label"] = df2["clean_label"].map(numeric_mapping)

# Etiqueta concatenada (debug / visualización)
df2["concatenated_label"] = df2.apply(
    lambda row: f"{row['numeric_label']}: {row['clean_label']}",
    axis=1
)

# Guardar
df2.to_csv("dataset/label_list_clean_with_numeric.csv", index=False)

# Debug
print(df2[['label_list', 'clean_label', 'numeric_label', 'concatenated_label']].head(20))
print("\nDistribución de clases:")
print(df2['numeric_label'].value_counts().sort_index())
     label_list clean_label  numeric_label concatenated_label
0    background  background              0      0: background
1   accessories      ignore            255        255: ignore
2           bag      ignore            255        255: ignore
3          belt      ignore            255        255: ignore
4        blazer     topwear              1         1: topwear
5        blouse     topwear              1         1: topwear
6      bodysuit     topwear              1         1: topwear
7         boots    footwear              4        4: footwear
8           bra      ignore            255        255: ignore
9      bracelet      ignore            255        255: ignore
10         cape     topwear              1         1: topwear
11     cardigan     topwear              1         1: topwear
12        clogs    footwear              4        4: footwear
13         coat     topwear              1         1: topwear
14        dress       dress              3           3: dress
15     earrings      ignore            255        255: ignore
16        flats    footwear              4        4: footwear
17      glasses      ignore            255        255: ignore
18       gloves      ignore            255        255: ignore
19         hair        body              5            5: body

Distribución de clases:
numeric_label
0       1
1      15
2       7
3       2
4      10
5       2
255    22
Name: count, dtype: int64
In [177]:
df2
Out[177]:
Unnamed: 0 label_list clean_label numeric_label concatenated_label
0 0 background background 0 0: background
1 1 accessories ignore 255 255: ignore
2 2 bag ignore 255 255: ignore
3 3 belt ignore 255 255: ignore
4 4 blazer topwear 1 1: topwear
5 5 blouse topwear 1 1: topwear
6 6 bodysuit topwear 1 1: topwear
7 7 boots footwear 4 4: footwear
8 8 bra ignore 255 255: ignore
9 9 bracelet ignore 255 255: ignore
10 10 cape topwear 1 1: topwear
11 11 cardigan topwear 1 1: topwear
12 12 clogs footwear 4 4: footwear
13 13 coat topwear 1 1: topwear
14 14 dress dress 3 3: dress
15 15 earrings ignore 255 255: ignore
16 16 flats footwear 4 4: footwear
17 17 glasses ignore 255 255: ignore
18 18 gloves ignore 255 255: ignore
19 19 hair body 5 5: body
20 20 hat ignore 255 255: ignore
21 21 heels footwear 4 4: footwear
22 22 hoodie topwear 1 1: topwear
23 23 intimate ignore 255 255: ignore
24 24 jacket topwear 1 1: topwear
25 25 jeans lowerwear 2 2: lowerwear
26 26 jumper ignore 255 255: ignore
27 27 leggings lowerwear 2 2: lowerwear
28 28 loafers footwear 4 4: footwear
29 29 necklace ignore 255 255: ignore
30 30 panties lowerwear 2 2: lowerwear
31 31 pants lowerwear 2 2: lowerwear
32 32 pumps footwear 4 4: footwear
33 33 purse ignore 255 255: ignore
34 34 ring ignore 255 255: ignore
35 35 romper dress 3 3: dress
36 36 sandals footwear 4 4: footwear
37 37 scarf ignore 255 255: ignore
38 38 shirt topwear 1 1: topwear
39 39 shoes footwear 4 4: footwear
40 40 shorts lowerwear 2 2: lowerwear
41 41 skin body 5 5: body
42 42 skirt lowerwear 2 2: lowerwear
43 43 sneakers footwear 4 4: footwear
44 44 socks ignore 255 255: ignore
45 45 stockings ignore 255 255: ignore
46 46 suit topwear 1 1: topwear
47 47 sunglasses ignore 255 255: ignore
48 48 sweater topwear 1 1: topwear
49 49 sweatshirt topwear 1 1: topwear
50 50 swimwear ignore 255 255: ignore
51 51 t-shirt topwear 1 1: topwear
52 52 tie ignore 255 255: ignore
53 53 tights lowerwear 2 2: lowerwear
54 54 top topwear 1 1: topwear
55 55 vest topwear 1 1: topwear
56 56 wallet ignore 255 255: ignore
57 57 watch ignore 255 255: ignore
58 58 wedges footwear 4 4: footwear
In [178]:
df2.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 59 entries, 0 to 58
Data columns (total 5 columns):
 #   Column              Non-Null Count  Dtype 
---  ------              --------------  ----- 
 0   Unnamed: 0          59 non-null     int64 
 1   label_list          59 non-null     object
 2   clean_label         59 non-null     object
 3   numeric_label       59 non-null     int64 
 4   concatenated_label  59 non-null     object
dtypes: int64(2), object(3)
memory usage: 2.4+ KB
In [179]:
df2['numeric_label'].value_counts
Out[179]:
<bound method IndexOpsMixin.value_counts of 0       0
1     255
2     255
3     255
4       1
5       1
6       1
7       4
8     255
9     255
10      1
11      1
12      4
13      1
14      3
15    255
16      4
17    255
18    255
19      5
20    255
21      4
22      1
23    255
24      1
25      2
26    255
27      2
28      4
29    255
30      2
31      2
32      4
33    255
34    255
35      3
36      4
37    255
38      1
39      4
40      2
41      5
42      2
43      4
44    255
45    255
46      1
47    255
48      1
49      1
50    255
51      1
52    255
53      2
54      1
55      1
56    255
57    255
58      4
Name: numeric_label, dtype: int64>
In [180]:
train_tf = A.Compose([
    # --- Resize y Random Crop (evita que modelo aprenda tamaño fijo) ---
    # A.Resize(320, 320),
    # A.RandomCrop(256, 256, p=0.8),
    
    A.RandomResizedCrop(
        size=(256, 256),
        scale=(0.7, 1.0),
        ratio=(0.75, 1.33),
        p=1.0
    ),

    # --- Transformaciones geométricas más robustas ---
    A.OneOf([
        A.ShiftScaleRotate(
            shift_limit=0.1, 
            scale_limit=0.2, 
            rotate_limit=15,
            border_mode=cv2.BORDER_CONSTANT,
            value=0,
            mask_value=255,
            p=1.0
        ),
        
        A.GridDistortion(
            num_steps=5, 
            distort_limit=0.3,
            border_mode=cv2.BORDER_CONSTANT,
            value=0,
            mask_value=255,
            p=0.3
        ),
    ], p=0.8),
    
    A.HorizontalFlip(p=0.5),
    
    # --- Color y textura más agresivos ---
    A.OneOf([
        A.RandomBrightnessContrast(
            brightness_limit=0.3, 
            contrast_limit=0.3
        ),
        A.HueSaturationValue(
            hue_shift_limit=20,
            sat_shift_limit=30,
            val_shift_limit=20
        ),
        A.RGBShift(
            r_shift_limit=20,
            g_shift_limit=20,
            b_shift_limit=20
        ),
    ], p=0.5),
    
    # --- Ruido y desenfoque ---
    A.OneOf([
        A.GaussianBlur(blur_limit=(3, 7)),
        A.MedianBlur(blur_limit=5),
        A.MotionBlur(blur_limit=7),
    ], p=0.3),
    
    A.GaussNoise(var_limit=(10.0, 40.0), p=0.2),
    
    # --- Compresión y calidad (simula diferentes fuentes) ---
    A.ImageCompression(
        quality_lower=60, 
        quality_upper=95, 
        p=0.2
    ),
    
    # --- Cutout / CoarseDropout (regularización espacial) ---
    A.CoarseDropout(
        max_holes=8, 
        max_height=32, 
        max_width=32,
        min_holes=1,
        min_height=8,
        min_width=8,
        fill_value=0,
        mask_fill_value=255,
        p=0.3
    ),
    
    # --- Normalización ---
    A.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    ),
    
    ToTensorV2()
])

val_tf = A.Compose([
    A.Resize(256, 256),
    A.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    ),
    ToTensorV2()
])
c:\Users\pc\Desktop\Proyecto_Segmentacion\.venv\Lib\site-packages\albumentations\core\validation.py:114: UserWarning: ShiftScaleRotate is a special case of Affine transform. Please use Affine transform instead.
  original_init(self, **validated_kwargs)
C:\Users\pc\AppData\Local\Temp\ipykernel_12992\3931023381.py:15: UserWarning: Argument(s) 'value, mask_value' are not valid for transform ShiftScaleRotate
  A.ShiftScaleRotate(
C:\Users\pc\AppData\Local\Temp\ipykernel_12992\3931023381.py:25: UserWarning: Argument(s) 'value, mask_value' are not valid for transform GridDistortion
  A.GridDistortion(
C:\Users\pc\AppData\Local\Temp\ipykernel_12992\3931023381.py:62: UserWarning: Argument(s) 'var_limit' are not valid for transform GaussNoise
  A.GaussNoise(var_limit=(10.0, 40.0), p=0.2),
C:\Users\pc\AppData\Local\Temp\ipykernel_12992\3931023381.py:65: UserWarning: Argument(s) 'quality_lower, quality_upper' are not valid for transform ImageCompression
  A.ImageCompression(
C:\Users\pc\AppData\Local\Temp\ipykernel_12992\3931023381.py:72: UserWarning: Argument(s) 'max_holes, max_height, max_width, min_holes, min_height, min_width, fill_value, mask_fill_value' are not valid for transform CoarseDropout
  A.CoarseDropout(
In [181]:
root = "C:\\Users\\pc\\Desktop\\Proyecto_Segmentacion\\dataset"
csv_labels_path = "C:\\Users\\pc\\Desktop\\Proyecto_Segmentacion\\dataset\\label_list_clean_with_numeric.csv"
In [182]:
class CustomSegmentationDataset(Dataset):

    def __init__(self, root, transformations=None, csv_labels_path=None):

        # Paths
        self.im_paths = sorted(glob(os.path.join(root, "png_images", "IMAGES", "*.png")))
        self.gt_paths = sorted(glob(os.path.join(root, "png_masks", "MASKS", "*.png")))

        assert len(self.im_paths) == len(self.gt_paths), \
            "Cantidad de imágenes y máscaras no coincide."

        self.transformations = transformations
        self.ignore_index = 255

        # ============================
        # 1. CSV limpio
        # ============================
        
        
        self.id_to_numeric = dict(zip(df2["Unnamed: 0"], df2["numeric_label"]))


        # id original → label limpio
        self.id_to_clean = dict(zip(df2["Unnamed: 0"], df2["clean_label"]))
        
        # Obtener clases válidas (excluyendo ignore)
        valid_classes_df2 = df2[df2["numeric_label"] != self.ignore_index].copy()
        
        # Info del dataset
        self.n_cls = len(
            sorted(df2[df2["numeric_label"] != self.ignore_index]["numeric_label"].unique())
        )
        
        # Usar con sus ids de su base
        self.label_to_idx = dict(zip(valid_classes_df2["clean_label"], valid_classes_df2["numeric_label"])
)
       
        
        # Mapeo inverso para decodificar predicciones
        self.idx_to_label = {idx: cls for cls, idx in self.label_to_idx.items()}
        
        self.n_cls = len(self.label_to_idx)
        
    
        
            
    
    def __len__(self):
        return len(self.im_paths)

    def __getitem__(self, idx):

        im = np.array(Image.open(self.im_paths[idx]).convert("RGB"))
        gt = np.array(Image.open(self.gt_paths[idx]))

        # ============================
        # 2. Remapeo con ignore_index
        # ============================
        remapped = np.full_like(gt, fill_value=self.ignore_index, dtype=np.int64)

        # Aplicar el mapeo directamente
        for original_id, numeric_label in self.id_to_numeric.items():
            remapped[gt == original_id] = numeric_label

        gt = remapped
        
        # Asegurar que gt sea int64
        gt = gt.astype(np.int64)

        # Transformaciones
        if self.transformations:
            augmented = self.transformations(image=im, mask=gt)
            im, gt = augmented["image"], augmented["mask"]
            
        # Asegurar tipo después de transformaciones
        if torch.is_tensor(gt):
            gt = gt.long()  # Convertir a Long
        elif isinstance(gt, np.ndarray):
            gt = gt.astype(np.int64)
            
        # Asegurar que las imágenes estén en float32
        if torch.is_tensor(im):
            im = im.float()
            

        return im, gt
    
    def get_class_names(self):
            """Retorna los nombres de las clases en orden"""
            return [self.idx_to_label[i] for i in range(self.n_cls)]
        
    
    
In [183]:
def get_dls(
    root,
    train_tf,
    val_tf,
    bs,
    split=(0.8, 0.1, 0.1),
    ns=2,
    csv_labels_path=None,
    seed=42,
    device="cpu"
):
    # Dataset base (solo info)
    base_ds = CustomSegmentationDataset(
        root=root,
        transformations=None,
        csv_labels_path=csv_labels_path
    )
    

    dataset_info = {
        'n_cls': base_ds.n_cls,
        'ignore_index': base_ds.ignore_index,
        'class_names': base_ds.get_class_names(),
        'label_to_idx': base_ds.label_to_idx,
        'idx_to_label': base_ds.idx_to_label
    }   

    
    print(dataset_info)

    # reproducibilidad
    generator = torch.Generator().manual_seed(seed)

    tr_len = int(len(base_ds) * split[0])
    val_len = int(len(base_ds) * split[1])
    test_len = len(base_ds) - tr_len - val_len

    # tr_ds, val_ds, test_ds = torch.utils.data.random_split(
    #     base_ds, [tr_len, val_len, test_len], generator=generator
    # )
    
    indices = torch.randperm(len(base_ds), generator=generator)

    tr_idx = indices[:tr_len]
    val_idx = indices[tr_len:tr_len + val_len]
    test_idx = indices[tr_len + val_len:]


    
    print(f"\nDIVISIÓN DEL DATASET (seed={seed}):")
    print(f"Train: {len(tr_idx)} muestras ({split[0]*100:.1f}%)")
    print(f"Val:   {len(val_idx)} muestras ({split[1]*100:.1f}%)")
    print(f"Test:  {len(test_idx)} muestras ({split[2]*100:.1f}%)")
    
    
    # =====================================================
    # 3. Crear datasets SEPARADOS con transforms correctos
    # =====================================================
    # Datasets con transforms correctos
    train_ds = torch.utils.data.Subset(
        CustomSegmentationDataset(root, train_tf, csv_labels_path),
        tr_idx
    )

    val_ds = torch.utils.data.Subset(
        CustomSegmentationDataset(root, val_tf, csv_labels_path),
        val_idx
    )

    test_ds = torch.utils.data.Subset(
        CustomSegmentationDataset(root, val_tf, csv_labels_path),
        test_idx
    )
    
    # =====================================================
    # 4. Dataloaders
    # =====================================================
    pin = (device == "cuda")

    tr_dl = DataLoader(train_ds, bs, shuffle=True,
                       num_workers=ns, pin_memory=pin,
                       persistent_workers=(ns > 0))

    val_dl = DataLoader(val_ds, bs, shuffle=False,
                        num_workers=ns, pin_memory=pin,
                        persistent_workers=(ns > 0))

    test_dl = DataLoader(test_ds, 1, shuffle=False,
                         num_workers=ns, pin_memory=pin,
                         persistent_workers=(ns > 0))

    # VERIFICAR DISTRIBUCIÓN DE CLASES EN SPLITS
    # ============================================
    def get_class_distribution(subset):
        """Calcula distribución de clases en un subset"""
        all_masks = []
        for i in range(min(100, len(subset))):  # Muestrear hasta 100
            _, mask = subset[i]
            if torch.is_tensor(mask):
                mask = mask.numpy()
            all_masks.append(mask.flatten())
        
        if all_masks:
            concatenated = np.concatenate(all_masks)
            unique, counts = np.unique(concatenated, return_counts=True)
            dist = dict(zip(unique, counts))
            
            # Convertir índices a nombres de clase
            dist_named = {}
            for idx, count in dist.items():
                if idx == base_ds.ignore_index:
                    dist_named['ignore'] = count
                elif idx in base_ds.idx_to_label:
                    dist_named[base_ds.idx_to_label[idx]] = count
                else:
                    dist_named[str(idx)] = count
            
            return dist_named
        return {}
    
    print("\nDistribución aproximada de clases (primeras 100 muestras):")
    print("Train:", get_class_distribution(train_ds))
    print("Val:  ", get_class_distribution(val_ds))
    print("Test: ", get_class_distribution(test_ds))


        
    print("\n" + "=" * 50)
    print("DATALOADERS CREADOS EXITOSAMENTE")
    print("=" * 50)
    

    return tr_dl, val_dl, test_dl, dataset_info
In [184]:
device = "cuda" if torch.cuda.is_available() else "cpu"

tr_dl, val_dl, test_dl, info = get_dls(
    root=root,
    train_tf=train_tf,
    val_tf=val_tf,
    bs=4,
    ns=0,
    csv_labels_path=csv_labels_path,
    device=device
)
{'n_cls': 6, 'ignore_index': 255, 'class_names': ['background', 'topwear', 'lowerwear', 'dress', 'footwear', 'body'], 'label_to_idx': {'background': 0, 'topwear': 1, 'footwear': 4, 'dress': 3, 'body': 5, 'lowerwear': 2}, 'idx_to_label': {0: 'background', 1: 'topwear', 4: 'footwear', 3: 'dress', 5: 'body', 2: 'lowerwear'}}

DIVISIÓN DEL DATASET (seed=42):
Train: 800 muestras (80.0%)
Val:   100 muestras (10.0%)
Test:  100 muestras (10.0%)

Distribución aproximada de clases (primeras 100 muestras):
Train: {'background': np.int64(4805028), 'topwear': np.int64(655546), 'lowerwear': np.int64(354726), 'dress': np.int64(233653), 'footwear': np.int64(48321), 'body': np.int64(297725), 'ignore': np.int64(158601)}
Val:   {'background': np.int64(5037874), 'topwear': np.int64(486014), 'lowerwear': np.int64(299220), 'dress': np.int64(224666), 'footwear': np.int64(73741), 'body': np.int64(274670), 'ignore': np.int64(157415)}
Test:  {'background': np.int64(5058909), 'topwear': np.int64(547670), 'lowerwear': np.int64(304042), 'dress': np.int64(157740), 'footwear': np.int64(65676), 'body': np.int64(265605), 'ignore': np.int64(153958)}

==================================================
DATALOADERS CREADOS EXITOSAMENTE
==================================================
In [185]:
# índice = clase
CLASS_COLORS = [
    [0, 0, 0],        # 0 background → negro
    [220, 20, 60],    # 1 topwear → rojo (crimson)
    [30, 144, 255],   # 2 lowerwear → azul
    [255, 165, 0],    # 3 dress → naranja
    [138, 43, 226],   # 4 footwear → violeta
    [46, 139, 87],    # 5 body → verde (sea green)
]

# Leyenda alineada con el dataset
CLASS_LEGEND = [
    ("background", "negro", 0),
    ("topwear", "rojo", 1),
    ("lowerwear", "azul", 2),
    ("dress", "naranja", 3),
    ("footwear", "violeta", 4),
    ("body", "verde", 5),
]

VISUALIZACION DE LA DATA¶

In [186]:
# ---------------------------------------------------------
# Invierte normalización ImageNet → devuelve imagen RGB uint8
# ---------------------------------------------------------
def to_numpy_image(t):
    inv = tfs.Compose([
        tfs.Normalize(mean=[0,0,0], std=[1/0.229, 1/0.224, 1/0.225]),
        tfs.Normalize(mean=[-0.485, -0.456, -0.406], std=[1,1,1])
    ])
    t = inv(t)
    t = (t * 255).clamp(0,255).byte().cpu().permute(1,2,0).numpy()
    return t

# ---------------------------------------------------------
# Convierte máscara (tensor HxW con clases) a imagen visible
# ---------------------------------------------------------
def mask_to_rgb(mask, n_cls, ignore_index=255):
    """
    Convierte una máscara HxW (clases) a RGB usando colores fijos
    
    Args:
        mask: Tensor o numpy array de forma (H, W)
        n_cls: Número de clases válidas
        ignore_index: Valor para píxeles ignorados (default: 255)
    
    Returns:
        RGB image de forma (H, W, 3)
    """
    if torch.is_tensor(mask):
        mask = mask.cpu().numpy()
    
    h, w = mask.shape
    rgb = np.zeros((h, w, 3), dtype=np.int64)
    
    
    for cls_idx in range(n_cls):
        rgb[mask == cls_idx] = CLASS_COLORS[cls_idx]
    
    # ignore_index → gris oscuro (para distinguir del background)
    rgb[mask == ignore_index] = [64, 64, 64]
    
    return rgb

# ---------------------------------------------------------
# Plot helper
# ---------------------------------------------------------
def plot(ax, image, title=""):
    ax.imshow(image)
    ax.set_title(title)
    ax.axis("off")

# ---------------------------------------------------------
# Versión 1: Visualizar dataset original
# ---------------------------------------------------------
def visualize_dataset(dataset, n_ims=8):
    """
    Visualiza muestras del dataset original (CustomSegmentationDataset)
    """
    import math
    
    total_plots = n_ims * 2
    cols = 4
    rows = math.ceil(total_plots / cols)
    
    fig, axs = plt.subplots(rows, cols, figsize=(20, rows * 4))
    axs = axs.flatten()
    
    # Seleccionar índices aleatorios
    indices = random.sample(range(len(dataset)), n_ims)
    
    i = 0
    for idx in indices:
        im, gt = dataset[idx]
        
        # Convertir imagen y máscara
        rgb = to_numpy_image(im.float())
        gt_img = mask_to_rgb(gt, dataset.n_cls, dataset.ignore_index)
        
        plot(axs[i], rgb, f"Imagen {idx}")
        i += 1
        plot(axs[i], gt_img, f"Máscara (clases {np.unique(gt)})")
        i += 1
        
        if i >= len(axs):
            break
    
    # Ocultar ejes vacíos
    for j in range(i, len(axs)):
        axs[j].axis('off')
    
    plt.suptitle(f"Dataset - {n_cls} clases | ignore_index={dataset.ignore_index}", fontsize=16)
    plt.tight_layout()
    plt.show()
    
    # Mostrar estadísticas de las muestras visualizadas
    print("=" * 60)
    print(f"VISUALIZACIÓN - {n_ims} muestras del dataset")
    print("=" * 60)
    for idx in indices:
        im, gt = dataset[idx]
        unique_vals = torch.unique(gt) if torch.is_tensor(gt) else np.unique(gt)
        valid_classes = [v for v in unique_vals if v != dataset.ignore_index and v < dataset.n_cls]
        ignore_pixels = np.sum(gt == dataset.ignore_index) if hasattr(gt, '__len__') else 0
        
        print(f"Muestra {idx}: Clases presentes: {valid_classes}, Píxeles ignorados: {ignore_pixels}")

# ---------------------------------------------------------
# Versión 2: Visualizar Subset (de DataLoader)
# ---------------------------------------------------------
def visualize_subset(subset, n_ims=8):
    """
    Visualiza muestras de un Subset (como tr_dl.dataset, val_dl.dataset)
    """
    import math
    
    total_plots = n_ims * 2
    cols = 4
    rows = math.ceil(total_plots / cols)
    
    fig, axs = plt.subplots(rows, cols, figsize=(20, rows * 4))
    axs = axs.flatten()
    
    # Seleccionar índices aleatorios dentro del subset
    indices = random.sample(range(len(subset)), n_ims)
    
    i = 0
    for plot_idx, subset_idx in enumerate(indices):
        # Obtener muestra del subset
        im, gt = subset[subset_idx]
        
        # Convertir imagen y máscara
        rgb = to_numpy_image(im.float())
        
        # Necesitamos acceder al dataset original para obtener n_cls e ignore_index
        if hasattr(subset, 'dataset') and hasattr(subset.dataset, 'n_cls'):
            gt_img = mask_to_rgb(gt, subset.dataset.n_cls, subset.dataset.ignore_index)
        else:
            # Fallback si no podemos acceder al dataset original
            gt_img = mask_to_rgb(gt, n_cls, 255)
        
        plot(axs[i], rgb, f"Imagen {plot_idx+1}")
        i += 1
        plot(axs[i], gt_img, f"Máscara {plot_idx+1}")
        i += 1
        
        if i >= len(axs):
            break
    
    # Ocultar ejes vacíos
    for j in range(i, len(axs)):
        axs[j].axis('off')
    
    plt.suptitle(f"Subset - {n_cls} clases", fontsize=16)
    plt.tight_layout()
    plt.show()

# ---------------------------------------------------------
# Versión 3: Visualizar DataLoader (batch)
# ---------------------------------------------------------
def visualize_dataloader(dataloader, n_batches=1):
    """
    Visualiza batches completos de un DataLoader
    """
    for batch_idx, (images, masks) in enumerate(dataloader):
        if batch_idx >= n_batches:
            break
            
        batch_size = images.shape[0]
        cols = 4
        rows = math.ceil(batch_size * 2 / cols)
        
        fig, axs = plt.subplots(rows, cols, figsize=(20, rows * 4))
        axs = axs.flatten()
        
        i = 0
        for sample_idx in range(batch_size):
            img = images[sample_idx]
            mask = masks[sample_idx]
            
            rgb = to_numpy_image(img.float())
            
            # Acceder a n_cls desde el dataset
            if hasattr(dataloader.dataset, 'dataset'):
                gt_img = mask_to_rgb(mask, dataloader.dataset.dataset.n_cls, 
                                   dataloader.dataset.dataset.ignore_index)
            else:
                gt_img = mask_to_rgb(mask, n_cls, 255)
            
            plot(axs[i], rgb, f"Batch {batch_idx}, Img {sample_idx}")
            i += 1
            plot(axs[i], gt_img, f"Mask {sample_idx}")
            i += 1
            
            if i >= len(axs):
                break
        
        # Ocultar ejes vacíos
        for j in range(i, len(axs)):
            axs[j].axis('off')
        
        plt.suptitle(f"Batch {batch_idx} - Shape: {images.shape}", fontsize=16)
        plt.tight_layout()
        plt.show()
        
        # Estadísticas del batch
        print(f"\nBatch {batch_idx}:")
        print(f"  Images shape: {images.shape}")
        print(f"  Masks shape: {masks.shape}")
        
        unique_vals = torch.unique(masks)
        print(f"  Unique mask values: {unique_vals.tolist()}")
        
        for val in unique_vals:
            count = (masks == val).sum().item()
            if val == 255:
                print(f"    ignore (255): {count} píxeles")
            elif val < n_cls:
                print(f"    clase {val}: {count} píxeles")


# Asegurarnos de que n_cls e ignore_index estén definidos en el scope global
if 'n_cls' not in globals():
    # Preferimos tomarlo del dict `info` si está disponible
    try:
        n_cls = info.get('n_cls', None)
    except Exception:
        n_cls = None
    if n_cls is None:
        # Fallback: usar el número de class_names si existe, o un valor por defecto razonable
        n_cls = len(info.get('class_names', [])) if isinstance(info, dict) and 'class_names' in info else 6

if 'ignore_index' not in globals():
    try:
        ignore_index = info.get('ignore_index', 255)
    except Exception:
        ignore_index = 255

print("\n" + "=" * 60)
print("VISUALIZANDO SUBSET DE ENTRENAMIENTO")
print("=" * 60)
visualize_subset(tr_dl.dataset, n_ims=8)

print("\n" + "=" * 60)
print("VISUALIZANDO SUBSET DE VALIDACIÓN")
print("=" * 60)
visualize_subset(val_dl.dataset, n_ims=4)

# Opción C: Visualizar un batch del dataloader
print("\n" + "=" * 60)
print("VISUALIZANDO BATCH DEL DATALOADER DE ENTRENAMIENTO")
print("=" * 60)
visualize_dataloader(tr_dl, n_batches=1)

# ---------------------------------------------------------
# Función específica para tu caso actual
# ---------------------------------------------------------
def visualize_current():
    """
    Función específica para visualizar con tu configuración actual
    """
    print("=" * 70)
    print("VISUALIZACIÓN DE DATOS - CONFIGURACIÓN ACTUAL")
    print("=" * 70)
    print(f"Número de clases: {n_cls}")
    print(f"Device: {device}")
    print(f"Batch size train: {tr_dl.batch_size}")
    print(f"Batch size val: {val_dl.batch_size}")
    print(f"Batch size test: {test_dl.batch_size}")
    
    # Verificar acceso a ignore_index
    if hasattr(tr_dl.dataset.dataset, 'ignore_index'):
        ignore_idx = tr_dl.dataset.dataset.ignore_index
        print(f"Ignore index: {ignore_idx}")
    else:
        print("Ignore index: 255 (default)")
        ignore_idx = 255
    
    # Visualizar algunas muestras de entrenamiento
    import math
    
    n_samples = 6
    cols = 4
    rows = math.ceil(n_samples * 2 / cols)
    
    fig, axs = plt.subplots(rows, cols, figsize=(20, rows * 4))
    axs = axs.flatten()
    
    indices = random.sample(range(len(tr_dl.dataset)), n_samples)
    
    i = 0
    for plot_idx, subset_idx in enumerate(indices):
        im, gt = tr_dl.dataset[subset_idx]
        
        rgb = to_numpy_image(im.float())
        gt_img = mask_to_rgb(gt, n_cls, ignore_idx)
        
        # Obtener clases presentes en la máscara
        if torch.is_tensor(gt):
            unique_vals = torch.unique(gt).cpu().numpy()
        else:
            unique_vals = np.unique(gt)
        
        valid_classes = [int(v) for v in unique_vals if v != ignore_idx and v < n_cls]
        
        plot(axs[i], rgb, f"Train Img {plot_idx+1}")
        i += 1
        plot(axs[i], gt_img, f"Mask {plot_idx+1} (cls: {valid_classes})")
        i += 1
        
        if i >= len(axs):
            break
    
    for j in range(i, len(axs)):
        axs[j].axis('off')
    
    plt.suptitle(f'Dataset de Entrenamiento - {n_cls} clases (ignore={ignore_idx})', fontsize=16)
    plt.tight_layout()
    plt.show()
    
    # Mostrar leyenda de colores
    print("\n" + "=" * 60)
    print("LEGENDA DE COLORES PARA LAS MÁSCARAS")
    print("=" * 60)
    
    for cls_name, color_name, cls_idx in CLASS_LEGEND:
        print(f"  Clase {cls_idx:2d}: {cls_name:12s} → {color_name}")
        print(f"  Ignore (255): gris oscuro")

# Ejecutar visualización
visualize_current()
============================================================
VISUALIZANDO SUBSET DE ENTRENAMIENTO
============================================================
No description has been provided for this image
============================================================
VISUALIZANDO SUBSET DE VALIDACIÓN
============================================================
No description has been provided for this image
============================================================
VISUALIZANDO BATCH DEL DATALOADER DE ENTRENAMIENTO
============================================================
No description has been provided for this image
Batch 0:
  Images shape: torch.Size([4, 3, 256, 256])
  Masks shape: torch.Size([4, 256, 256])
  Unique mask values: [0, 1, 2, 3, 4, 5, 255]
    clase 0: 197169 píxeles
    clase 1: 35344 píxeles
    clase 2: 15007 píxeles
    clase 3: 3175 píxeles
    clase 4: 896 píxeles
    clase 5: 6648 píxeles
    ignore (255): 3905 píxeles
======================================================================
VISUALIZACIÓN DE DATOS - CONFIGURACIÓN ACTUAL
======================================================================
Número de clases: 6
Device: cpu
Batch size train: 4
Batch size val: 4
Batch size test: 1
Ignore index: 255
No description has been provided for this image
============================================================
LEGENDA DE COLORES PARA LAS MÁSCARAS
============================================================
  Clase  0: background   → negro
  Ignore (255): gris oscuro
  Clase  1: topwear      → rojo
  Ignore (255): gris oscuro
  Clase  2: lowerwear    → azul
  Ignore (255): gris oscuro
  Clase  3: dress        → naranja
  Ignore (255): gris oscuro
  Clase  4: footwear     → violeta
  Ignore (255): gris oscuro
  Clase  5: body         → verde
  Ignore (255): gris oscuro

Setup de Entrenamiento¶

In [187]:
class OptimizedSegmentationModel(nn.Module):
    """
    Modelo optimizado para CPU con DeepLabV3Plus
    """
    def __init__(self, n_cls=6, encoder_name="resnet34",encoder_weights="imagenet", dropout_rate=0.3):
        super().__init__()
        
    
        self.model = smp.DeepLabV3Plus(
            encoder_name=encoder_name,
            encoder_weights=encoder_weights,  # Usar pesos preentrenados
            in_channels=3,
            classes=n_cls,
            activation=None, 
            encoder_dropout=dropout_rate,  # Dropout en encoder
            decoder_dropout=dropout_rate,   # Dropout en decoder
        )
        
        self.output_dropout = nn.Dropout2d(p=dropout_rate * 0.5)
        # BatchNorm extra después del modelo
       
        
        print(f"Modelo creado: DeepLabV3Plus con {encoder_name}")
        print(f"Número de parámetros: {self.count_parameters():,}")
        print(f"Clases: {n_cls}")
        print(f"Modelo con dropout={dropout_rate}")
       
        
       
    
    def forward(self, x):
        x = self.model(x)
        return x
       
    
    def count_parameters(self):
        return sum(p.numel() for p in self.parameters() if p.requires_grad)

# Crear el modelo
n_cls = 6  # Deberías obtener esto de tu dataset
model = OptimizedSegmentationModel(n_cls=n_cls, encoder_name="resnet34")

# Mover a CPU
device = "cpu"
model.to(device)
print(f"Modelo en dispositivo: {device}")


class Metrics():
    
    def __init__(self, pred, gt, loss_fn, n_cls=6, ignore_index=255, eps=1e-8):
        self.pred_logits = pred
        self.pred = torch.argmax(pred, dim=1)  # (batch, H, W)
        
        # Asegurar que gt tenga la forma correcta
        if gt.dim() == 4 and gt.size(1) == 1:
            self.gt = gt.squeeze(1).long()
        elif gt.dim() == 4 and gt.size(1) > 1:
            # Si es one-hot, convertimos
            self.gt = torch.argmax(gt, dim=1).long()
        else:
            self.gt = gt.long()
        
        self.loss_fn = loss_fn
        self.n_cls = n_cls
        self.ignore_index = ignore_index
        self.eps = eps
        
        # Mover todo a CPU para cálculos de métricas
        if self.pred_logits.is_cuda:
            self.pred_logits = self.pred_logits.cpu()
            self.pred = self.pred.cpu()
            self.gt = self.gt.cpu()
            
    def IoU_per_class_detailed(self):
        """IoU por clase con estadísticas detalladas"""
        with torch.no_grad():
            valid_mask = self.gt != self.ignore_index
            
            if valid_mask.sum().item() == 0:
                return {i: {"iou": 0.0, "pixels": 0} for i in range(self.n_cls)}
            
            pred_valid = self.pred[valid_mask]
            gt_valid = self.gt[valid_mask]
            
            results = {}
            
            for cls in range(self.n_cls):
                pred_cls = pred_valid == cls
                gt_cls = gt_valid == cls
                
                intersection = (pred_cls & gt_cls).sum().float().item()
                union = (pred_cls | gt_cls).sum().float().item()
                total_pixels_cls = gt_cls.sum().float().item()
                
                if union > 0:
                    iou = (intersection + self.eps) / (union + self.eps)
                else:
                    iou = 0.0
                
                # Precisión por clase
                if pred_cls.sum() > 0:
                    precision = intersection / pred_cls.sum().float().item()
                else:
                    precision = 0.0
                
                # Recall por clase
                if total_pixels_cls > 0:
                    recall = intersection / total_pixels_cls
                else:
                    recall = 0.0
                
                results[cls] = {
                    "iou": iou,
                    "precision": precision,
                    "recall": recall,
                    "pixels": total_pixels_cls,
                    "predicted_pixels": pred_cls.sum().float().item()
                }
        
        return results        
    
    def PA(self):
        """Pixel Accuracy (Precisión por píxel)"""
        with torch.no_grad():
            # Filtrar píxeles ignorados
            valid_mask = self.gt != self.ignore_index
            
            if valid_mask.sum().item() == 0:
                return 0.0
            
            correct = (self.pred == self.gt) & valid_mask
            accuracy = correct.sum().float() / valid_mask.sum().float()
            
        return accuracy.item()
    
    def mIoU(self):
        """Mean Intersection over Union"""
        with torch.no_grad():
            # Filtrar píxeles ignorados
            valid_mask = self.gt != self.ignore_index
            
            if valid_mask.sum().item() == 0:
                return 0.0
            
            pred_valid = self.pred[valid_mask]
            gt_valid = self.gt[valid_mask]
            
            ious = []
            
            for cls in range(self.n_cls):
                pred_cls = pred_valid == cls
                gt_cls = gt_valid == cls
                
                intersection = (pred_cls & gt_cls).sum().float().item()
                union = (pred_cls | gt_cls).sum().float().item()
                
                if union > 0:
                    iou = (intersection + self.eps) / (union + self.eps)
                    ious.append(iou)
                else:
                    # Si no hay píxeles de esta clase, omitimos
                    continue
            
            if len(ious) == 0:
                return 0.0
            
            miou = sum(ious) / len(ious)
        
        return miou
    
    def IoU_per_class(self):
        """IoU por clase"""
        with torch.no_grad():
            valid_mask = self.gt != self.ignore_index
            
            if valid_mask.sum().item() == 0:
                return {f"class_{i}": 0.0 for i in range(self.n_cls)}
            
            pred_valid = self.pred[valid_mask]
            gt_valid = self.gt[valid_mask]
            
            ious = {}
            
            for cls in range(self.n_cls):
                pred_cls = pred_valid == cls
                gt_cls = gt_valid == cls
                
                intersection = (pred_cls & gt_cls).sum().float().item()
                union = (pred_cls | gt_cls).sum().float().item()
                
                if union > 0:
                    iou = (intersection + self.eps) / (union + self.eps)
                    ious[f"class_{cls}"] = iou
                else:
                    ious[f"class_{cls}"] = 0.0
        
        return ious
    
   
    
Modelo creado: DeepLabV3Plus con resnet34
Número de parámetros: 22,438,742
Clases: 6
Modelo con dropout=0.3
Modelo en dispositivo: cpu
In [188]:
def calculate_class_weights(dataset, n_cls=6, ignore_index=255):
    pixel_counts = np.zeros(n_cls, dtype=np.int64)
    total_pixels = 0

    for i in range(len(dataset)):
        _, mask = dataset[i]
        if torch.is_tensor(mask):
            mask = mask.numpy()

        valid = mask != ignore_index
        total_pixels += valid.sum()

        for cls in range(n_cls):
            pixel_counts[cls] += np.sum(mask[valid] == cls)

    class_freq = pixel_counts / (total_pixels + 1e-6)

    class_weights = 1.0 / (class_freq + 1e-6)
    class_weights = class_weights / class_weights.sum() * n_cls
    class_weights = np.clip(class_weights, 0.5, 5.0)

    print("Distribución de clases:", class_freq)
    print("Pesos calculados:", class_weights)

    return torch.tensor(class_weights, dtype=torch.float32)
In [189]:
# Calcular pesos de clase desde el dataset (tr_dl puede ser un DataLoader)
dataset_for_weights = tr_dl.dataset if isinstance(tr_dl, DataLoader) else tr_dl

class_weights = calculate_class_weights(dataset_for_weights, n_cls=n_cls, ignore_index=255)

# Mover a dispositivo si es un tensor
if isinstance(class_weights, torch.Tensor):
    class_weights = class_weights.to(device)

print("Class weights:", class_weights)
Distribución de clases: [0.7524836  0.10086926 0.0586283  0.03802228 0.00668313 0.04331343]
Pesos calculados: [0.5        0.5        0.5        0.69424743 3.94928803 0.60944051]
Class weights: tensor([0.5000, 0.5000, 0.5000, 0.6942, 3.9493, 0.6094])
In [190]:
class DiceLoss(nn.Module):
    def __init__(
        self,
        mode="multiclass",
        classes=None,
        log_loss=False,
        smooth=1e-6,
        ignore_index=255
    ):
        super().__init__()
        assert mode == "multiclass", "Esta implementación es solo para multiclase"
        self.classes = classes
        self.log_loss = log_loss
        self.smooth = smooth
        self.ignore_index = ignore_index

    def forward(self, logits, target):
        """
        logits: (B, C, H, W)
        target: (B, H, W)
        """
        num_classes = logits.shape[1]
    
        # Máscara válida
        valid_mask = target != self.ignore_index  # (B, H, W)
    
        # Reemplazar ignore_index por una clase válida dummy (0)
        target_safe = target.clone()
        target_safe[target_safe == self.ignore_index] = 0
    
        # Softmax
        probs = F.softmax(logits, dim=1)
    
        # One-hot SEGURO
        target_one_hot = F.one_hot(
            target_safe, num_classes=num_classes
        ).permute(0, 3, 1, 2).float()
    
        # Aplicar máscara
        valid_mask = valid_mask.unsqueeze(1)
        probs = probs * valid_mask
        target_one_hot = target_one_hot * valid_mask
    
        # Dice
        dims = (0, 2, 3)
        intersection = torch.sum(probs * target_one_hot, dims)
        cardinality = torch.sum(probs + target_one_hot, dims)
    
        dice = (2.0 * intersection + self.smooth) / (cardinality + self.smooth)
    
        if self.classes is not None:
            dice = dice[self.classes]
    
        loss = 1.0 - dice
        return loss.mean()
In [191]:
class FocalLoss(nn.Module):
    def __init__(
        self,
        gamma=2.0,
        weight=None,
        ignore_index=255,
        reduction="mean"
    ):
        super().__init__()
        self.gamma = gamma
        self.ignore_index = ignore_index
        self.reduction = reduction
        self.ce = nn.CrossEntropyLoss(
            weight=weight,
            ignore_index=ignore_index,
            reduction="none"
        )

    def forward(self, logits, target):
        """
        logits: (B, C, H, W)
        target: (B, H, W)
        """
        ce_loss = self.ce(logits, target)  # (B, H, W)

        # pt = probabilidad del target correcto
        pt = torch.exp(-ce_loss)

        focal_loss = ((1 - pt) ** self.gamma) * ce_loss

        if self.reduction == "mean":
            return focal_loss.mean()
        elif self.reduction == "sum":
            return focal_loss.sum()
        else:
            return focal_loss
In [192]:
class CombinedLoss(nn.Module):
    def __init__(self, class_weights, n_cls=6, ignore_index = 255):
        super().__init__()

        self.ce_loss = nn.CrossEntropyLoss(
            weight=class_weights,
            ignore_index=ignore_index
        )

        self.dice_loss = DiceLoss(
            mode="multiclass",
            classes=list(range(n_cls)),
            log_loss=False,
            ignore_index=ignore_index
        )

        self.focal_loss = FocalLoss(
            gamma=2.0,
            weight=class_weights,
            ignore_index=ignore_index
        )

        self.weights = [0.4, 0.3, 0.3]

    def forward(self, pred, target):
        ce = self.ce_loss(pred, target)
        dice = self.dice_loss(pred, target)
        focal = self.focal_loss(pred, target)

        return (
            self.weights[0] * ce +
            self.weights[1] * dice +
            self.weights[2] * focal
        )
In [193]:
def get_optimizer_and_scheduler(model, lr=3e-4):
    # Optimizador con warmup
    optimizer = torch.optim.AdamW(
        model.parameters(),
        lr=lr,
        weight_decay=1e-4,  # Regularización L2
        betas=(0.9, 0.999)
    )
    
    # Scheduler con warmup y cosine annealing
    scheduler = torch.optim.lr_scheduler.OneCycleLR(
        optimizer,
        max_lr=lr,
        epochs=80,
        steps_per_epoch=len(tr_dl),
        pct_start=0.1,  # 10% warmup
        anneal_strategy='cos'
    )
    
    return optimizer, scheduler

Entrenamiento y validacion¶

In [194]:
class CPUTrainer:
    """
    Trainer optimizado para entrenamiento en CPU
    """
    def __init__(self, model, train_loader, val_loader, 
                 criterion, optimizer, scheduler=None,
                 device="cpu", n_cls=6, ignore_index=255,patience=10,delta=0.001,min_epochs=20):
        
        self.model = model
        self.train_loader = train_loader
        self.val_loader = val_loader
        self.criterion = criterion
        self.optimizer = optimizer
        self.scheduler = scheduler
        self.device = device
        self.n_cls = n_cls
        self.ignore_index = ignore_index
        
        # Parámetros para Early Stopping
        self.patience = patience  # Número de épocas sin mejora para detener
        self.delta = delta  # Cambio mínimo para considerar mejora
        self.min_epochs = min_epochs
        self.counter = 0  # Contador de épocas sin mejora
        self.best_score = None
        self.best_state = None
        self.early_stop = False
        
        # Historial
        self.history = {
            'train_loss': [],
            'val_loss': [],
            'train_pa': [],
            'val_pa': [],
            'train_miou': [],
            'val_miou': [],
            'lr': [],
            'per_class_iou': []
        }
    
    def _early_stopping(self, val_miou,val_loss, epoch):
        """Early stopping mejorado"""
        # No considerar early stopping antes de min_epochs
        if epoch < self.min_epochs:
            return False
            
        if self.best_score is None:
            self.best_score = val_miou
            # Guardar estado del modelo
            self.best_state = {
                'model_state': self.model.state_dict(),
                'optimizer_state': self.optimizer.state_dict(),
                'epoch': epoch,
                'score': val_miou,
                'val_loss_min': val_loss
            }
            return False
            
        elif val_miou < self.best_score + self.delta:
            self.counter += 1
            print(f"   EarlyStopping: {self.counter}/{self.patience} sin mejora")
            
            if self.counter >= self.patience:
                self.early_stop = True
                # Restaurar mejor modelo
                if self.best_state:
                    self.model.load_state_dict(self.best_state['model_state'])
                    self.optimizer.load_state_dict(self.best_state['optimizer_state'])
                return True
        else:
            print(f"  ✓ Mejora significativa: {val_miou:.4f} > {self.best_score:.4f}")
            self.best_score = val_miou
            self.counter = 0
            # Actualizar mejor estado
            self.best_state = {
                'model_state': self.model.state_dict(),
                'optimizer_state': self.optimizer.state_dict(),
                'epoch': epoch,
                'score': val_miou
            }
            
        return False
        
    def train_epoch(self):
        """Entrena una época"""
        self.model.train()
        total_loss = 0.0
        total_pa = 0.0
        total_miou = 0.0
        num_batches = 0
        
        for batch_idx, (images, masks) in enumerate(self.train_loader):
            # Mover a dispositivo
            images = images.to(self.device).float()
            masks = masks.to(self.device).long()
            
            # Forward pass
            self.optimizer.zero_grad()
            outputs = self.model(images)
            
            # Calcular pérdida
            loss = self.criterion(outputs, masks)
            
            # Backward pass
            loss.backward()
            torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
            self.optimizer.step()
            self.scheduler.step() # AQUÍ
            
            
            
            
            
            # Calcular métricas
            with torch.no_grad():
                metrics = Metrics(outputs, masks, self.criterion,
                                 n_cls=self.n_cls, 
                                 ignore_index=self.ignore_index)
                pa = metrics.PA()
                miou = metrics.mIoU()
            
            total_loss += loss.item()
            total_pa += pa
            total_miou += miou
            num_batches += 1
            
            # Log cada cierto número de batches
            if (batch_idx + 1) % 10 == 0:
                print(f"  Batch {batch_idx+1}/{len(self.train_loader)}: "
                      f"Loss: {loss.item():.4f}, PA: {pa:.4f}, mIoU: {miou:.4f}")
        
        return {
            'loss': total_loss / max(num_batches,1),
            'pa': total_pa / max(num_batches,1),
            'miou': total_miou / max(num_batches,1)
        }
    
    def validate(self):
        """Validación"""
        self.model.eval()
        total_loss = 0.0
        total_pa = 0.0
        total_miou = 0.0
        num_batches = 0
        
        with torch.no_grad():
            for images, masks in self.val_loader:
                images = images.to(self.device).float()
                masks = masks.to(self.device).long()
                
                outputs = self.model(images)
                loss = self.criterion(outputs, masks)
                
                metrics = Metrics(outputs, masks, self.criterion,
                                 n_cls=self.n_cls,
                                 ignore_index=self.ignore_index)
                pa = metrics.PA()
                miou = metrics.mIoU()
                
                total_loss += loss.item()
                total_pa += pa
                total_miou += miou
                num_batches += 1
        
        return {
            'loss': total_loss / max(num_batches,1),
            'pa': total_pa / max(num_batches,1),
            'miou': total_miou / max(num_batches,1)
        }
    
    def compute_epoch_iou(self):
        intersection = torch.zeros(self.n_cls, device=self.device)
        union = torch.zeros(self.n_cls, device=self.device)
    
        with torch.no_grad():
            for images, masks in self.val_loader:
                images = images.to(self.device)
                masks = masks.to(self.device)
    
                outputs = self.model(images)
                preds = torch.argmax(outputs, dim=1)
    
                valid = masks != self.ignore_index
    
                for cls in range(self.n_cls):
                    pred_cls = (preds == cls) & valid
                    gt_cls = (masks == cls) & valid
    
                    intersection[cls] += (pred_cls & gt_cls).sum()
                    union[cls] += (pred_cls | gt_cls).sum()
    
        return {
            f"class_{i}": (intersection[i] / (union[i] + 1e-6)).item()
            for i in range(self.n_cls)
        }
    
    
    
    def train(self, num_epochs, save_path="best_model_mejorado.pth"):
        """Entrenamiento completo"""
        best_miou = 0.0
        
        print("\n" + "="*60)
        print("INICIANDO ENTRENAMIENTO EN CPU")
        print("="*60)
        
        print(f"Early Stopping configurado: paciencia={self.patience}, delta={self.delta}")
        
        for epoch in range(num_epochs):
            start_time = time.time()
            
            print(f"\nEpoch {epoch+1}/{num_epochs}")
            print("-"*40)
            
            # Entrenar
            train_metrics = self.train_epoch()
            
            # Validar
            val_metrics = self.validate()
            
            # Calcular métricas por clase
            with torch.no_grad():
            # Tomar un batch de validación
                for images, masks in self.val_loader:
                    images = images.to(self.device)
                    masks = masks.to(self.device)
                
                    outputs = self.model(images)
                    metrics = Metrics(outputs, masks, self.criterion,
                                n_cls=self.n_cls, 
                                ignore_index=self.ignore_index)
                per_class_iou = metrics.IoU_per_class_detailed()
                
            
            # Guardar en historial
            self.history['train_loss'].append(train_metrics['loss'])
            self.history['val_loss'].append(val_metrics['loss'])
            self.history['train_pa'].append(train_metrics['pa'])
            self.history['val_pa'].append(val_metrics['pa'])
            self.history['train_miou'].append(train_metrics['miou'])
            self.history['val_miou'].append(val_metrics['miou'])
            self.history['lr'].append(self.optimizer.param_groups[0]['lr'])
            self.history['per_class_iou'].append(per_class_iou)
            
            # Tiempo de la época
            epoch_time = time.time() - start_time
            
            # Mostrar resultados
            print(f"\nResumen Epoch {epoch+1}:")
            print(f"  Tiempo: {epoch_time:.2f}s")
            print(f"  Train Loss: {train_metrics['loss']:.4f} | "
                  f"PA: {train_metrics['pa']:.4f} | "
                  f"mIoU: {train_metrics['miou']:.4f}")
            print(f"  Val Loss:   {val_metrics['loss']:.4f} | "
                  f"PA: {val_metrics['pa']:.4f} | "
                  f"mIoU: {val_metrics['miou']:.4f}")
            print(f"  Learning Rate: {self.optimizer.param_groups[0]['lr']:.6f}")
            
            # Guardar mejor modelo
            if val_metrics['miou'] > best_miou:
                best_miou = val_metrics['miou']
                torch.save({
                    'epoch': epoch,
                    'model_state_dict': self.model.state_dict(),
                    'optimizer_state_dict': self.optimizer.state_dict(),
                    'val_miou': best_miou,
                    'history': self.history,
                }, save_path)
                print(f"  ✓ Modelo guardado (mIoU: {best_miou:.4f})")
            
            

            
            # Verificar Early Stopping (pasando el número de época)
            if self._early_stopping(val_metrics['miou'], val_metrics['loss'], epoch):
                print(f"\n{'='*60}")
                print("EARLY STOPPING ACTIVADO")
                print(f"Sin mejora significativa por {self.patience} épocas")
                print(f"Mejor mIoU alcanzado: {best_miou:.4f}")
                print(f"Entrenamiento detenido en época {epoch+1}")
                print('='*60)
                break
        
        # print("\n" + "="*60)
        # print("ENTRENAMIENTO COMPLETADO")
        # print(f"Mejor mIoU: {best_miou:.4f}")
        # print("="*60)
        
        if not self.early_stop:
            print("\n" + "="*60)
            print("ENTRENAMIENTO COMPLETADO (sin early stopping)")
            print(f"Mejor mIoU: {best_miou:.4f}")
            print("="*60)
        
        return self.history
In [195]:
def plot_class_metrics(history, class_names):
    """Visualiza métricas por clase a través del tiempo"""
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # Extraer IoU por clase por época
    epochs = range(len(history['per_class_iou']))
    
    for cls, cls_name in enumerate(class_names):
        row = cls // 3
        col = cls % 3
        
        iou_values = []
        for epoch_iou in history['per_class_iou']:
            if cls in epoch_iou:
                iou_values.append(epoch_iou[cls]['iou'])
            else:
                iou_values.append(0)
        
        axes[row, col].plot(epochs, iou_values, 'b-', linewidth=2)
        axes[row, col].set_title(f'{cls_name} (Clase {cls})')
        axes[row, col].set_xlabel('Época')
        axes[row, col].set_ylabel('IoU')
        axes[row, col].grid(True, alpha=0.3)
        
        # Añadir mejor IoU
        best_iou = max(iou_values) if iou_values else 0
        axes[row, col].axhline(y=best_iou, color='r', linestyle='--', alpha=0.5)
        axes[row, col].text(0.5, 0.95, f'Mejor: {best_iou:.3f}', 
                          transform=axes[row, col].transAxes,
                          ha='center', va='top',
                          bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
    
    plt.suptitle('Evolución del IoU por Clase', fontsize=16)
    plt.tight_layout()
    plt.show()
In [196]:
# FUNCIÓN DE ENTRENAMIENTO
# ============================================
def entrenamiento(tr_dl, val_dl, n_cls=6, class_names=None):
    """
    Inicia el entrenamiento del modelo
    
    Args:
        tr_dl: DataLoader de entrenamiento
        val_dl: DataLoader de validación
        n_cls: Número de clases (debería ser 8 según tu mapeo)
    """
    # Configuración
    device = "cpu"
    ignore_index = 255
    num_epochs = 80  # a 100 Reducido para prueba, puedes aumentarlo
    learning_rate = 3e-4  #3e-4
    dropout_rate = 0.3
    
    # Parámetros para Early Stopping
    patience = 15  # Número de épocas sin mejora para detener
    delta = 0.001  # Mejora mínima requerida
    min_epochs = 15
    
    print("="*60)
    print("CONFIGURACIÓN DEL ENTRENAMIENTO")
    print("="*60)
    print(f"Dispositivo: {device}")
    print(f"Número de clases: {n_cls}")
    print(f"Índice ignore: {ignore_index}")
    print(f"Épocas: {num_epochs}")
    print(f"Early stopping: paciencia={patience}, delta={delta}")
    print(f"Learning rate: {learning_rate}")
    print(f"Tamaño batch entrenamiento: {tr_dl.batch_size}")
    print(f"Tamaño batch validación: {val_dl.batch_size}")
    print(f" Dropout: {dropout_rate}")
    
    print("\nCalculando pesos de clase...")
    
      
    
    
    # 1. Crear modelo - USAR RESNET18 EN LUGAR DE RESNET32
    print("\nCreando modelo...")
    model = OptimizedSegmentationModel(
        n_cls=n_cls,
        encoder_name="resnet34",  
        encoder_weights="imagenet",
        dropout_rate= dropout_rate
    ).to(device)
    
    class_weights = calculate_class_weights(tr_dl.dataset, n_cls, ignore_index).to(device)
    
    criterion = CombinedLoss(
        n_cls=n_cls,
        ignore_index=ignore_index,
        class_weights = class_weights,
        
    )
    
    # 4. Optimizador y OneCycleLR
    optimizer = torch.optim.AdamW(
        model.parameters(),
        lr=learning_rate,
        weight_decay=1e-4
    )
    
    scheduler = torch.optim.lr_scheduler.OneCycleLR(
        optimizer,
        max_lr=learning_rate,
        epochs=num_epochs,
        steps_per_epoch=len(tr_dl),
        pct_start=0.1,
        anneal_strategy='cos'
    )
    
    
    
    
    #  Crear trainer
    print("\nCreando trainer...")
    trainer = CPUTrainer(
        model=model,
        train_loader=tr_dl,
        val_loader=val_dl,
        criterion=criterion,
        optimizer=optimizer,
        scheduler=scheduler,
        device=device,
        n_cls=n_cls,
        ignore_index=ignore_index,
        patience=patience,
        delta=delta,
        min_epochs=min_epochs
    )
    
    # 6. Entrenar
    print("\nIniciando entrenamiento...")
    history = trainer.train(num_epochs=num_epochs, save_path="best_model_mejorado.pth")
    
    if class_names:
        plot_class_metrics(history, class_names)
        
    return model, history
In [197]:
def verificar_dataloaders(tr_dl, val_dl):
    """Verifica que los DataLoaders tengan los tipos y clases correctas"""
    print("=" * 60)
    print("VERIFICACIÓN DE DATALOADERS")
    print("=" * 60)

    # Mapeo id → nombre
    class_id_to_name = {
        0: "background",
        1: "topwear",
        2: "lowerwear",
        3: "dress",
        4: "footwear",
        5: "body",
        255: "ignore"
    }

    # -------------------------
    # Batch de entrenamiento
    # -------------------------
    images, masks = next(iter(tr_dl))

    print("\nBatch de entrenamiento:")
    print(f"  Images dtype: {images.dtype}, shape: {images.shape}")
    print(f"  Masks dtype:  {masks.dtype}, shape: {masks.shape}")
    print(f"  Images range: [{images.min():.3f}, {images.max():.3f}]")
    print(f"  Masks unique values: {torch.unique(masks).tolist()}")

    # Verificar tipo de máscara
    if masks.dtype != torch.int64:
        print(f"\n  ADVERTENCIA: Máscaras son {masks.dtype}, deberían ser torch.int64 (Long)")
        print("   Convirtiendo a Long...")
        masks = masks.long()
        print(f"   Nuevo dtype: {masks.dtype}")

    # -------------------------
    # Batch de validación
    # -------------------------
    images_val, masks_val = next(iter(val_dl))

    print("\nBatch de validación:")
    print(f"  Images dtype: {images_val.dtype}, shape: {images_val.shape}")
    print(f"  Masks dtype:  {masks_val.dtype}, shape: {masks_val.shape}")

    # -------------------------
    # Ignore index
    # -------------------------
    ignore_count_train = (masks == 255).sum().item()
    ignore_count_val = (masks_val == 255).sum().item()

    print("\nPíxeles ignorados (255):")
    print(f"  Train: {ignore_count_train}")
    print(f"  Val:   {ignore_count_val}")

    # -------------------------
    # Clases presentes (train)
    # -------------------------
    print("\nClases presentes en batch de entrenamiento:")
    unique_classes = torch.unique(masks)

    for cls in unique_classes:
        cls_id = int(cls.item())
        pixel_count = (masks == cls).sum().item()
        cls_name = class_id_to_name.get(cls_id, "UNKNOWN")

        print(f"  Clase {cls_id:>3} ({cls_name:<10}) → {pixel_count} píxeles")

    return True


# Paso 1: Verificar DataLoaders
print("Paso 1: Verificando DataLoaders...")
verificar_dataloaders(tr_dl, val_dl)

# Paso 2: Crear función de conversión de tipos si es necesario
def fix_dataloader_types(dataloader):
    """Función generadora que convierte máscaras a Long"""
    for images, masks in dataloader:
        yield images, masks.long()

def verificar_varios_batches(dataloader, n_batches=10):
    class_counts = {}
    for i, (_, masks) in enumerate(dataloader):
        if i >= n_batches:
            break
        for cls in torch.unique(masks):
            cls = int(cls.item())
            class_counts[cls] = class_counts.get(cls, 0) + 1

    print("\nClases vistas en", n_batches, "batches:")
    for cls, count in sorted(class_counts.items()):
        print(f"  Clase {cls}: aparece en {count} batches")
Paso 1: Verificando DataLoaders...
============================================================
VERIFICACIÓN DE DATALOADERS
============================================================

Batch de entrenamiento:
  Images dtype: torch.float32, shape: torch.Size([4, 3, 256, 256])
  Masks dtype:  torch.int64, shape: torch.Size([4, 256, 256])
  Images range: [-2.118, 2.640]
  Masks unique values: [0, 1, 2, 3, 4, 5, 255]

Batch de validación:
  Images dtype: torch.float32, shape: torch.Size([4, 3, 256, 256])
  Masks dtype:  torch.int64, shape: torch.Size([4, 256, 256])

Píxeles ignorados (255):
  Train: 4075
  Val:   6595

Clases presentes en batch de entrenamiento:
  Clase   0 (background) → 192303 píxeles
  Clase   1 (topwear   ) → 29850 píxeles
  Clase   2 (lowerwear ) → 9351 píxeles
  Clase   3 (dress     ) → 11511 píxeles
  Clase   4 (footwear  ) → 931 píxeles
  Clase   5 (body      ) → 14123 píxeles
  Clase 255 (ignore    ) → 4075 píxeles
In [198]:
# FUNCIÓN PARA VISUALIZAR RESULTADOS
# ============================================
def plot_training_history(history):
    """Visualiza el historial de entrenamiento"""
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # Pérdida
    axes[0, 0].plot(history['train_loss'], label='Train')
    axes[0, 0].plot(history['val_loss'], label='Val')
    axes[0, 0].set_title('Loss')
    axes[0, 0].set_xlabel('Epoch')
    axes[0, 0].set_ylabel('Loss')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)
    
    # Pixel Accuracy
    axes[0, 1].plot(history['train_pa'], label='Train')
    axes[0, 1].plot(history['val_pa'], label='Val')
    axes[0, 1].set_title('Pixel Accuracy')
    axes[0, 1].set_xlabel('Epoch')
    axes[0, 1].set_ylabel('PA')
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3)
    
    # mIoU
    axes[0, 2].plot(history['train_miou'], label='Train')
    axes[0, 2].plot(history['val_miou'], label='Val')
    axes[0, 2].set_title('Mean IoU')
    axes[0, 2].set_xlabel('Epoch')
    axes[0, 2].set_ylabel('mIoU')
    axes[0, 2].legend()
    axes[0, 2].grid(True, alpha=0.3)
    
    # Learning Rate
    axes[1, 0].plot(history['lr'])
    axes[1, 0].set_title('Learning Rate')
    axes[1, 0].set_xlabel('Epoch')
    axes[1, 0].set_ylabel('LR')
    axes[1, 0].grid(True, alpha=0.3)
    
    # Comparación Train vs Val
    axes[1, 1].plot(history['train_loss'], label='Train Loss', alpha=0.7)
    axes[1, 1].plot(history['val_loss'], label='Val Loss', alpha=0.7)
    axes[1, 1].plot(history['train_miou'], label='Train mIoU', alpha=0.7)
    axes[1, 1].plot(history['val_miou'], label='Val mIoU', alpha=0.7)
    axes[1, 1].set_title('Comparación Train vs Val')
    axes[1, 1].set_xlabel('Epoch')
    axes[1, 1].legend()
    axes[1, 1].grid(True, alpha=0.3)
    
    # Mejor época (basado en val_miou)
    best_epoch = np.argmax(history['val_miou'])
    axes[1, 2].text(0.1, 0.5, 
                   f"Mejor época: {best_epoch + 1}\n"
                   f"Mejor mIoU: {history['val_miou'][best_epoch]:.4f}\n"
                   f"Mejor PA: {history['val_pa'][best_epoch]:.4f}\n"
                   f"Loss en mejor época: {history['val_loss'][best_epoch]:.4f}",
                   fontsize=12, 
                   bbox=dict(boxstyle="round,pad=0.3", facecolor="lightblue"))
    axes[1, 2].axis('off')
    
    plt.suptitle('Historial de Entrenamiento', fontsize=16)
    plt.tight_layout()
    plt.show()

# ============================================
# EJECUTAR ENTRENAMIENTO
# ============================================

print("Verificando configuración...")
print(f"tr_dl definido: {'tr_dl' in locals() or 'tr_dl' in globals()}")
print(f"val_dl definido: {'val_dl' in locals() or 'val_dl' in globals()}")
print(f"n_cls: {n_cls if 'n_cls' in locals() or 'n_cls' in globals() else 'No definido'}")

# Si todo está bien, ejecutar entrenamiento
if 'tr_dl' in locals() and 'val_dl' in locals():
    try:
        
        # Iniciar entrenamiento
        model, history = entrenamiento(tr_dl, val_dl, n_cls=info['n_cls'],class_names=info['class_names'])
        
        # Visualizar resultados
        plot_training_history(history)
        
        print("\n¡Entrenamiento completado exitosamente!")
        print(f"Historial guardado. Mejor mIoU: {max(history['val_miou']):.4f}")
        
    except Exception as e:
        print(f"\nError durante el entrenamiento: {e}")
        
else:
    print("\nError: tr_dl y/o val_dl no están definidos")
    
    
Verificando configuración...
tr_dl definido: True
val_dl definido: True
n_cls: 6
============================================================
CONFIGURACIÓN DEL ENTRENAMIENTO
============================================================
Dispositivo: cpu
Número de clases: 6
Índice ignore: 255
Épocas: 80
Early stopping: paciencia=15, delta=0.001
Learning rate: 0.0003
Tamaño batch entrenamiento: 4
Tamaño batch validación: 4
 Dropout: 0.3

Calculando pesos de clase...

Creando modelo...
Modelo creado: DeepLabV3Plus con resnet34
Número de parámetros: 22,438,742
Clases: 6
Modelo con dropout=0.3
Distribución de clases: [0.75137613 0.10135447 0.05910229 0.03842691 0.00647621 0.04326399]
Pesos calculados: [0.5        0.5        0.5        0.67404747 3.99897941 0.5986881 ]

Creando trainer...

Iniciando entrenamiento...

============================================================
INICIANDO ENTRENAMIENTO EN CPU
============================================================
Early Stopping configurado: paciencia=15, delta=0.001

Epoch 1/80
----------------------------------------
  Batch 10/200: Loss: 1.1526, PA: 0.1242, mIoU: 0.0574
  Batch 20/200: Loss: 1.1461, PA: 0.1633, mIoU: 0.0639
  Batch 30/200: Loss: 1.1008, PA: 0.2276, mIoU: 0.0928
  Batch 40/200: Loss: 1.0502, PA: 0.3238, mIoU: 0.1254
  Batch 50/200: Loss: 1.0142, PA: 0.3959, mIoU: 0.1468
  Batch 60/200: Loss: 1.0091, PA: 0.4610, mIoU: 0.1598
  Batch 70/200: Loss: 0.9613, PA: 0.5551, mIoU: 0.2006
  Batch 80/200: Loss: 0.9346, PA: 0.5870, mIoU: 0.2105
  Batch 90/200: Loss: 0.8962, PA: 0.6403, mIoU: 0.2257
  Batch 100/200: Loss: 0.9261, PA: 0.5973, mIoU: 0.2126
  Batch 110/200: Loss: 0.8557, PA: 0.6883, mIoU: 0.2696
  Batch 120/200: Loss: 0.8637, PA: 0.7070, mIoU: 0.2563
  Batch 130/200: Loss: 0.8252, PA: 0.7101, mIoU: 0.2729
  Batch 140/200: Loss: 0.7822, PA: 0.7426, mIoU: 0.2795
  Batch 150/200: Loss: 0.7463, PA: 0.7733, mIoU: 0.3398
  Batch 160/200: Loss: 0.7139, PA: 0.7865, mIoU: 0.3230
  Batch 170/200: Loss: 0.7262, PA: 0.7558, mIoU: 0.3055
  Batch 180/200: Loss: 0.7378, PA: 0.7380, mIoU: 0.2381
  Batch 190/200: Loss: 0.6848, PA: 0.8033, mIoU: 0.3832
  Batch 200/200: Loss: 0.6969, PA: 0.7470, mIoU: 0.2859

Resumen Epoch 1:
  Tiempo: 438.13s
  Train Loss: 0.8994 | PA: 0.5601 | mIoU: 0.2198
  Val Loss:   0.5725 | PA: 0.8344 | mIoU: 0.4094
  Learning Rate: 0.000023
  ✓ Modelo guardado (mIoU: 0.4094)

Epoch 2/80
----------------------------------------
  Batch 10/200: Loss: 0.6381, PA: 0.8099, mIoU: 0.4051
  Batch 20/200: Loss: 0.5978, PA: 0.8134, mIoU: 0.4028
  Batch 30/200: Loss: 0.5952, PA: 0.8227, mIoU: 0.3970
  Batch 40/200: Loss: 0.5920, PA: 0.8094, mIoU: 0.3781
  Batch 50/200: Loss: 0.5484, PA: 0.8392, mIoU: 0.3912
  Batch 60/200: Loss: 0.5294, PA: 0.8464, mIoU: 0.4562
  Batch 70/200: Loss: 0.5742, PA: 0.8155, mIoU: 0.3611
  Batch 80/200: Loss: 0.4670, PA: 0.8816, mIoU: 0.5682
  Batch 90/200: Loss: 0.5962, PA: 0.8353, mIoU: 0.3906
  Batch 100/200: Loss: 0.5366, PA: 0.8087, mIoU: 0.3854
  Batch 110/200: Loss: 0.4807, PA: 0.8511, mIoU: 0.4567
  Batch 120/200: Loss: 0.5177, PA: 0.8363, mIoU: 0.3843
  Batch 130/200: Loss: 0.4798, PA: 0.8484, mIoU: 0.3798
  Batch 140/200: Loss: 0.5015, PA: 0.8330, mIoU: 0.4223
  Batch 150/200: Loss: 0.5163, PA: 0.8098, mIoU: 0.3671
  Batch 160/200: Loss: 0.4846, PA: 0.8198, mIoU: 0.3447
  Batch 170/200: Loss: 0.4216, PA: 0.8795, mIoU: 0.4457
  Batch 180/200: Loss: 0.4500, PA: 0.8512, mIoU: 0.4277
  Batch 190/200: Loss: 0.4793, PA: 0.8264, mIoU: 0.3337
  Batch 200/200: Loss: 0.4607, PA: 0.8195, mIoU: 0.3787

Resumen Epoch 2:
  Tiempo: 432.08s
  Train Loss: 0.5198 | PA: 0.8375 | mIoU: 0.4163
  Val Loss:   0.3570 | PA: 0.8947 | mIoU: 0.5182
  Learning Rate: 0.000054
  ✓ Modelo guardado (mIoU: 0.5182)

Epoch 3/80
----------------------------------------
  Batch 10/200: Loss: 0.3756, PA: 0.8835, mIoU: 0.5497
  Batch 20/200: Loss: 0.3951, PA: 0.8740, mIoU: 0.4361
  Batch 30/200: Loss: 0.4581, PA: 0.8359, mIoU: 0.4007
  Batch 40/200: Loss: 0.3914, PA: 0.8629, mIoU: 0.4792
  Batch 50/200: Loss: 0.3708, PA: 0.8655, mIoU: 0.5198
  Batch 60/200: Loss: 0.3421, PA: 0.8865, mIoU: 0.5335
  Batch 70/200: Loss: 0.3374, PA: 0.9049, mIoU: 0.5208
  Batch 80/200: Loss: 0.3696, PA: 0.8689, mIoU: 0.4595
  Batch 90/200: Loss: 0.4050, PA: 0.8583, mIoU: 0.4354
  Batch 100/200: Loss: 0.3898, PA: 0.8571, mIoU: 0.4511
  Batch 110/200: Loss: 0.3046, PA: 0.9286, mIoU: 0.5567
  Batch 120/200: Loss: 0.3636, PA: 0.8855, mIoU: 0.5042
  Batch 130/200: Loss: 0.3583, PA: 0.8882, mIoU: 0.4194
  Batch 140/200: Loss: 0.2925, PA: 0.9058, mIoU: 0.5595
  Batch 150/200: Loss: 0.3353, PA: 0.8682, mIoU: 0.5027
  Batch 160/200: Loss: 0.2612, PA: 0.9315, mIoU: 0.5523
  Batch 170/200: Loss: 0.3301, PA: 0.8886, mIoU: 0.4447
  Batch 180/200: Loss: 0.3573, PA: 0.8538, mIoU: 0.4916
  Batch 190/200: Loss: 0.3695, PA: 0.8589, mIoU: 0.4443
  Batch 200/200: Loss: 0.3692, PA: 0.8514, mIoU: 0.3937

Resumen Epoch 3:
  Tiempo: 405.45s
  Train Loss: 0.3614 | PA: 0.8802 | mIoU: 0.4916
  Val Loss:   0.2921 | PA: 0.9064 | mIoU: 0.5543
  Learning Rate: 0.000101
  ✓ Modelo guardado (mIoU: 0.5543)

Epoch 4/80
----------------------------------------
  Batch 10/200: Loss: 0.3414, PA: 0.8706, mIoU: 0.4485
  Batch 20/200: Loss: 0.4236, PA: 0.8432, mIoU: 0.4173
  Batch 30/200: Loss: 0.3514, PA: 0.8736, mIoU: 0.3842
  Batch 40/200: Loss: 0.2822, PA: 0.9074, mIoU: 0.5712
  Batch 50/200: Loss: 0.2916, PA: 0.9033, mIoU: 0.5434
  Batch 60/200: Loss: 0.2821, PA: 0.8981, mIoU: 0.5671
  Batch 70/200: Loss: 0.2911, PA: 0.9038, mIoU: 0.5191
  Batch 80/200: Loss: 0.3668, PA: 0.8514, mIoU: 0.4783
  Batch 90/200: Loss: 0.2448, PA: 0.9253, mIoU: 0.6134
  Batch 100/200: Loss: 0.3226, PA: 0.8848, mIoU: 0.5321
  Batch 110/200: Loss: 0.2594, PA: 0.9188, mIoU: 0.5635
  Batch 120/200: Loss: 0.2364, PA: 0.9169, mIoU: 0.6115
  Batch 130/200: Loss: 0.2325, PA: 0.9145, mIoU: 0.6097
  Batch 140/200: Loss: 0.3111, PA: 0.8747, mIoU: 0.4707
  Batch 150/200: Loss: 0.3487, PA: 0.8562, mIoU: 0.4132
  Batch 160/200: Loss: 0.3199, PA: 0.8664, mIoU: 0.4509
  Batch 170/200: Loss: 0.2391, PA: 0.9041, mIoU: 0.5705
  Batch 180/200: Loss: 0.2899, PA: 0.8771, mIoU: 0.4998
  Batch 190/200: Loss: 0.3561, PA: 0.8613, mIoU: 0.4790
  Batch 200/200: Loss: 0.3500, PA: 0.8733, mIoU: 0.4925

Resumen Epoch 4:
  Tiempo: 402.93s
  Train Loss: 0.2928 | PA: 0.8975 | mIoU: 0.5359
  Val Loss:   0.2282 | PA: 0.9155 | mIoU: 0.5730
  Learning Rate: 0.000156
  ✓ Modelo guardado (mIoU: 0.5730)

Epoch 5/80
----------------------------------------
  Batch 10/200: Loss: 0.3070, PA: 0.9007, mIoU: 0.4999
  Batch 20/200: Loss: 0.2303, PA: 0.9208, mIoU: 0.5466
  Batch 30/200: Loss: 0.2281, PA: 0.9152, mIoU: 0.6290
  Batch 40/200: Loss: 0.1734, PA: 0.9511, mIoU: 0.7584
  Batch 50/200: Loss: 0.2676, PA: 0.8924, mIoU: 0.5546
  Batch 60/200: Loss: 0.3133, PA: 0.8845, mIoU: 0.5226
  Batch 70/200: Loss: 0.2538, PA: 0.8864, mIoU: 0.5351
  Batch 80/200: Loss: 0.3667, PA: 0.8861, mIoU: 0.4458
  Batch 90/200: Loss: 0.1772, PA: 0.9400, mIoU: 0.7112
  Batch 100/200: Loss: 0.2686, PA: 0.9100, mIoU: 0.6473
  Batch 110/200: Loss: 0.2148, PA: 0.9177, mIoU: 0.6265
  Batch 120/200: Loss: 0.2944, PA: 0.8837, mIoU: 0.4831
  Batch 130/200: Loss: 0.2489, PA: 0.9186, mIoU: 0.5102
  Batch 140/200: Loss: 0.2337, PA: 0.9288, mIoU: 0.5899
  Batch 150/200: Loss: 0.3316, PA: 0.8492, mIoU: 0.4474
  Batch 160/200: Loss: 0.2935, PA: 0.8989, mIoU: 0.5226
  Batch 170/200: Loss: 0.2671, PA: 0.9059, mIoU: 0.5356
  Batch 180/200: Loss: 0.2354, PA: 0.9074, mIoU: 0.5871
  Batch 190/200: Loss: 0.2650, PA: 0.8917, mIoU: 0.5573
  Batch 200/200: Loss: 0.1596, PA: 0.9487, mIoU: 0.6959

Resumen Epoch 5:
  Tiempo: 406.86s
  Train Loss: 0.2676 | PA: 0.9047 | mIoU: 0.5548
  Val Loss:   0.2112 | PA: 0.9309 | mIoU: 0.6334
  Learning Rate: 0.000211
  ✓ Modelo guardado (mIoU: 0.6334)

Epoch 6/80
----------------------------------------
  Batch 10/200: Loss: 0.2583, PA: 0.9229, mIoU: 0.5788
  Batch 20/200: Loss: 0.2904, PA: 0.8830, mIoU: 0.5277
  Batch 30/200: Loss: 0.2217, PA: 0.8974, mIoU: 0.5873
  Batch 40/200: Loss: 0.2188, PA: 0.9112, mIoU: 0.5474
  Batch 50/200: Loss: 0.2675, PA: 0.8817, mIoU: 0.5016
  Batch 60/200: Loss: 0.3292, PA: 0.8696, mIoU: 0.5030
  Batch 70/200: Loss: 0.2100, PA: 0.9193, mIoU: 0.6026
  Batch 80/200: Loss: 0.2639, PA: 0.9050, mIoU: 0.5732
  Batch 90/200: Loss: 0.2184, PA: 0.9334, mIoU: 0.5870
  Batch 100/200: Loss: 0.3493, PA: 0.8755, mIoU: 0.5142
  Batch 110/200: Loss: 0.2175, PA: 0.9183, mIoU: 0.5786
  Batch 120/200: Loss: 0.2223, PA: 0.9300, mIoU: 0.6570
  Batch 130/200: Loss: 0.1678, PA: 0.9428, mIoU: 0.7014
  Batch 140/200: Loss: 0.1870, PA: 0.9329, mIoU: 0.6154
  Batch 150/200: Loss: 0.2264, PA: 0.9096, mIoU: 0.6116
  Batch 160/200: Loss: 0.2272, PA: 0.9117, mIoU: 0.5539
  Batch 170/200: Loss: 0.1891, PA: 0.9297, mIoU: 0.6429
  Batch 180/200: Loss: 0.2489, PA: 0.9084, mIoU: 0.5012
  Batch 190/200: Loss: 0.2395, PA: 0.9132, mIoU: 0.5177
  Batch 200/200: Loss: 0.2448, PA: 0.9037, mIoU: 0.5947

Resumen Epoch 6:
  Tiempo: 408.51s
  Train Loss: 0.2491 | PA: 0.9094 | mIoU: 0.5734
  Val Loss:   0.2047 | PA: 0.9231 | mIoU: 0.6057
  Learning Rate: 0.000258

Epoch 7/80
----------------------------------------
  Batch 10/200: Loss: 0.2349, PA: 0.9243, mIoU: 0.5205
  Batch 20/200: Loss: 0.2734, PA: 0.9114, mIoU: 0.5478
  Batch 30/200: Loss: 0.2666, PA: 0.9065, mIoU: 0.5168
  Batch 40/200: Loss: 0.1446, PA: 0.9598, mIoU: 0.7379
  Batch 50/200: Loss: 0.2683, PA: 0.9131, mIoU: 0.5523
  Batch 60/200: Loss: 0.3615, PA: 0.8791, mIoU: 0.4693
  Batch 70/200: Loss: 0.3184, PA: 0.8980, mIoU: 0.5127
  Batch 80/200: Loss: 0.2904, PA: 0.9019, mIoU: 0.5263
  Batch 90/200: Loss: 0.1832, PA: 0.9315, mIoU: 0.6382
  Batch 100/200: Loss: 0.2488, PA: 0.9160, mIoU: 0.5189
  Batch 110/200: Loss: 0.2272, PA: 0.9359, mIoU: 0.5125
  Batch 120/200: Loss: 0.2460, PA: 0.9164, mIoU: 0.5350
  Batch 130/200: Loss: 0.2406, PA: 0.9085, mIoU: 0.5141
  Batch 140/200: Loss: 0.2262, PA: 0.9122, mIoU: 0.6182
  Batch 150/200: Loss: 0.2284, PA: 0.9320, mIoU: 0.6635
  Batch 160/200: Loss: 0.2170, PA: 0.9209, mIoU: 0.6129
  Batch 170/200: Loss: 0.2940, PA: 0.8852, mIoU: 0.4458
  Batch 180/200: Loss: 0.2173, PA: 0.9203, mIoU: 0.5702
  Batch 190/200: Loss: 0.2030, PA: 0.9156, mIoU: 0.6378
  Batch 200/200: Loss: 0.1821, PA: 0.9303, mIoU: 0.6059

Resumen Epoch 7:
  Tiempo: 404.05s
  Train Loss: 0.2407 | PA: 0.9128 | mIoU: 0.5810
  Val Loss:   0.2079 | PA: 0.9329 | mIoU: 0.6224
  Learning Rate: 0.000289

Epoch 8/80
----------------------------------------
  Batch 10/200: Loss: 0.1840, PA: 0.9382, mIoU: 0.6700
  Batch 20/200: Loss: 0.4843, PA: 0.8845, mIoU: 0.4859
  Batch 30/200: Loss: 0.3177, PA: 0.8859, mIoU: 0.4575
  Batch 40/200: Loss: 0.2526, PA: 0.9140, mIoU: 0.5533
  Batch 50/200: Loss: 0.1947, PA: 0.9406, mIoU: 0.6603
  Batch 60/200: Loss: 0.2364, PA: 0.8897, mIoU: 0.5773
  Batch 70/200: Loss: 0.2058, PA: 0.9268, mIoU: 0.6510
  Batch 80/200: Loss: 0.1991, PA: 0.9176, mIoU: 0.6618
  Batch 90/200: Loss: 0.3946, PA: 0.8530, mIoU: 0.3516
  Batch 100/200: Loss: 0.2303, PA: 0.9160, mIoU: 0.5262
  Batch 110/200: Loss: 0.2212, PA: 0.9131, mIoU: 0.6006
  Batch 120/200: Loss: 0.1654, PA: 0.9567, mIoU: 0.7347
  Batch 130/200: Loss: 0.3353, PA: 0.8771, mIoU: 0.4703
  Batch 140/200: Loss: 0.2143, PA: 0.9287, mIoU: 0.5877
  Batch 150/200: Loss: 0.2109, PA: 0.9209, mIoU: 0.5993
  Batch 160/200: Loss: 0.2010, PA: 0.9327, mIoU: 0.7020
  Batch 170/200: Loss: 0.2686, PA: 0.9189, mIoU: 0.5003
  Batch 180/200: Loss: 0.1740, PA: 0.9449, mIoU: 0.6764
  Batch 190/200: Loss: 0.2614, PA: 0.9287, mIoU: 0.5308
  Batch 200/200: Loss: 0.3409, PA: 0.8579, mIoU: 0.4197

Resumen Epoch 8:
  Tiempo: 411.39s
  Train Loss: 0.2394 | PA: 0.9143 | mIoU: 0.5843
  Val Loss:   0.2024 | PA: 0.9297 | mIoU: 0.6184
  Learning Rate: 0.000300

Epoch 9/80
----------------------------------------
  Batch 10/200: Loss: 0.2581, PA: 0.8903, mIoU: 0.5768
  Batch 20/200: Loss: 0.3206, PA: 0.8538, mIoU: 0.4839
  Batch 30/200: Loss: 0.2411, PA: 0.9183, mIoU: 0.6854
  Batch 40/200: Loss: 0.2191, PA: 0.8973, mIoU: 0.5384
  Batch 50/200: Loss: 0.2001, PA: 0.9289, mIoU: 0.6669
  Batch 60/200: Loss: 0.3257, PA: 0.8759, mIoU: 0.4385
  Batch 70/200: Loss: 0.2104, PA: 0.9101, mIoU: 0.6437
  Batch 80/200: Loss: 0.1509, PA: 0.9504, mIoU: 0.7243
  Batch 90/200: Loss: 0.3110, PA: 0.8877, mIoU: 0.4532
  Batch 100/200: Loss: 0.1969, PA: 0.9376, mIoU: 0.6727
  Batch 110/200: Loss: 0.2246, PA: 0.9225, mIoU: 0.7711
  Batch 120/200: Loss: 0.2282, PA: 0.9118, mIoU: 0.5699
  Batch 130/200: Loss: 0.2414, PA: 0.9452, mIoU: 0.5559
  Batch 140/200: Loss: 0.2127, PA: 0.9331, mIoU: 0.6371
  Batch 150/200: Loss: 0.1769, PA: 0.9501, mIoU: 0.6967
  Batch 160/200: Loss: 0.2842, PA: 0.8864, mIoU: 0.5143
  Batch 170/200: Loss: 0.2251, PA: 0.9262, mIoU: 0.5603
  Batch 180/200: Loss: 0.2025, PA: 0.9438, mIoU: 0.5464
  Batch 190/200: Loss: 0.2128, PA: 0.9270, mIoU: 0.6181
  Batch 200/200: Loss: 0.1709, PA: 0.9455, mIoU: 0.7726

Resumen Epoch 9:
  Tiempo: 406.68s
  Train Loss: 0.2316 | PA: 0.9173 | mIoU: 0.5926
  Val Loss:   0.1863 | PA: 0.9387 | mIoU: 0.6520
  Learning Rate: 0.000300
  ✓ Modelo guardado (mIoU: 0.6520)

Epoch 10/80
----------------------------------------
  Batch 10/200: Loss: 0.2577, PA: 0.9151, mIoU: 0.5493
  Batch 20/200: Loss: 0.2248, PA: 0.9016, mIoU: 0.6062
  Batch 30/200: Loss: 0.2230, PA: 0.9246, mIoU: 0.5437
  Batch 40/200: Loss: 0.2035, PA: 0.9278, mIoU: 0.5839
  Batch 50/200: Loss: 0.1956, PA: 0.9367, mIoU: 0.5876
  Batch 60/200: Loss: 0.2185, PA: 0.9308, mIoU: 0.5671
  Batch 70/200: Loss: 0.2704, PA: 0.9016, mIoU: 0.6225
  Batch 80/200: Loss: 0.2581, PA: 0.8985, mIoU: 0.5452
  Batch 90/200: Loss: 0.1633, PA: 0.9579, mIoU: 0.6808
  Batch 100/200: Loss: 0.1822, PA: 0.9422, mIoU: 0.6333
  Batch 110/200: Loss: 0.2671, PA: 0.8847, mIoU: 0.4714
  Batch 120/200: Loss: 0.1905, PA: 0.9477, mIoU: 0.6029
  Batch 130/200: Loss: 0.1746, PA: 0.9348, mIoU: 0.6901
  Batch 140/200: Loss: 0.1510, PA: 0.9472, mIoU: 0.7404
  Batch 150/200: Loss: 0.1981, PA: 0.9398, mIoU: 0.6603
  Batch 160/200: Loss: 0.4107, PA: 0.8758, mIoU: 0.4689
  Batch 170/200: Loss: 0.2790, PA: 0.9030, mIoU: 0.4561
  Batch 180/200: Loss: 0.1606, PA: 0.9410, mIoU: 0.7067
  Batch 190/200: Loss: 0.2833, PA: 0.9249, mIoU: 0.5815
  Batch 200/200: Loss: 0.2018, PA: 0.9306, mIoU: 0.7184

Resumen Epoch 10:
  Tiempo: 400.95s
  Train Loss: 0.2164 | PA: 0.9220 | mIoU: 0.6145
  Val Loss:   0.1798 | PA: 0.9405 | mIoU: 0.6807
  Learning Rate: 0.000299
  ✓ Modelo guardado (mIoU: 0.6807)

Epoch 11/80
----------------------------------------
  Batch 10/200: Loss: 0.2268, PA: 0.8923, mIoU: 0.5445
  Batch 20/200: Loss: 0.1425, PA: 0.9506, mIoU: 0.7533
  Batch 30/200: Loss: 0.3565, PA: 0.8618, mIoU: 0.4652
  Batch 40/200: Loss: 0.2637, PA: 0.8989, mIoU: 0.5464
  Batch 50/200: Loss: 0.1492, PA: 0.9446, mIoU: 0.7318
  Batch 60/200: Loss: 0.1867, PA: 0.9343, mIoU: 0.5722
  Batch 70/200: Loss: 0.2093, PA: 0.9243, mIoU: 0.5922
  Batch 80/200: Loss: 0.1476, PA: 0.9407, mIoU: 0.6889
  Batch 90/200: Loss: 0.2200, PA: 0.9365, mIoU: 0.6244
  Batch 100/200: Loss: 0.1250, PA: 0.9612, mIoU: 0.7893
  Batch 110/200: Loss: 0.2398, PA: 0.8785, mIoU: 0.5192
  Batch 120/200: Loss: 0.1806, PA: 0.9348, mIoU: 0.5854
  Batch 130/200: Loss: 0.1910, PA: 0.9272, mIoU: 0.6201
  Batch 140/200: Loss: 0.2173, PA: 0.9277, mIoU: 0.5960
  Batch 150/200: Loss: 0.2577, PA: 0.8876, mIoU: 0.5066
  Batch 160/200: Loss: 0.1792, PA: 0.9403, mIoU: 0.6330
  Batch 170/200: Loss: 0.1875, PA: 0.9526, mIoU: 0.6252
  Batch 180/200: Loss: 0.2162, PA: 0.9199, mIoU: 0.5497
  Batch 190/200: Loss: 0.2522, PA: 0.8889, mIoU: 0.4990
  Batch 200/200: Loss: 0.1912, PA: 0.9238, mIoU: 0.6690

Resumen Epoch 11:
  Tiempo: 418.49s
  Train Loss: 0.2185 | PA: 0.9225 | mIoU: 0.6131
  Val Loss:   0.1971 | PA: 0.9353 | mIoU: 0.6583
  Learning Rate: 0.000299

Epoch 12/80
----------------------------------------
  Batch 10/200: Loss: 0.2388, PA: 0.9021, mIoU: 0.5104
  Batch 20/200: Loss: 0.1855, PA: 0.9340, mIoU: 0.6537
  Batch 30/200: Loss: 0.1754, PA: 0.9330, mIoU: 0.7681
  Batch 40/200: Loss: 0.1591, PA: 0.9431, mIoU: 0.6992
  Batch 50/200: Loss: 0.2129, PA: 0.9207, mIoU: 0.5651
  Batch 60/200: Loss: 0.1937, PA: 0.9280, mIoU: 0.6125
  Batch 70/200: Loss: 0.1370, PA: 0.9662, mIoU: 0.7363
  Batch 80/200: Loss: 0.2125, PA: 0.9323, mIoU: 0.5633
  Batch 90/200: Loss: 0.2010, PA: 0.9289, mIoU: 0.7050
  Batch 100/200: Loss: 0.2174, PA: 0.9083, mIoU: 0.5410
  Batch 110/200: Loss: 0.1691, PA: 0.9410, mIoU: 0.6171
  Batch 120/200: Loss: 0.2234, PA: 0.9305, mIoU: 0.5616
  Batch 130/200: Loss: 0.3129, PA: 0.8822, mIoU: 0.5413
  Batch 140/200: Loss: 0.1847, PA: 0.9514, mIoU: 0.5925
  Batch 150/200: Loss: 0.2074, PA: 0.9225, mIoU: 0.5621
  Batch 160/200: Loss: 0.2351, PA: 0.9252, mIoU: 0.5681
  Batch 170/200: Loss: 0.1562, PA: 0.9540, mIoU: 0.7518
  Batch 180/200: Loss: 0.1911, PA: 0.9281, mIoU: 0.6832
  Batch 190/200: Loss: 0.1352, PA: 0.9679, mIoU: 0.6857
  Batch 200/200: Loss: 0.3185, PA: 0.9051, mIoU: 0.5255

Resumen Epoch 12:
  Tiempo: 402.62s
  Train Loss: 0.2128 | PA: 0.9259 | mIoU: 0.6228
  Val Loss:   0.1727 | PA: 0.9406 | mIoU: 0.6680
  Learning Rate: 0.000298

Epoch 13/80
----------------------------------------
  Batch 10/200: Loss: 0.2193, PA: 0.9173, mIoU: 0.6258
  Batch 20/200: Loss: 0.2857, PA: 0.8775, mIoU: 0.5886
  Batch 30/200: Loss: 0.1531, PA: 0.9548, mIoU: 0.6492
  Batch 40/200: Loss: 0.4464, PA: 0.8918, mIoU: 0.4789
  Batch 50/200: Loss: 0.1252, PA: 0.9549, mIoU: 0.7378
  Batch 60/200: Loss: 0.2186, PA: 0.9194, mIoU: 0.5884
  Batch 70/200: Loss: 0.2334, PA: 0.8929, mIoU: 0.5856
  Batch 80/200: Loss: 0.1847, PA: 0.9409, mIoU: 0.6956
  Batch 90/200: Loss: 0.2588, PA: 0.9283, mIoU: 0.5700
  Batch 100/200: Loss: 0.2073, PA: 0.9409, mIoU: 0.6639
  Batch 110/200: Loss: 0.2636, PA: 0.9103, mIoU: 0.5381
  Batch 120/200: Loss: 0.2588, PA: 0.9112, mIoU: 0.5036
  Batch 130/200: Loss: 0.1731, PA: 0.9352, mIoU: 0.6689
  Batch 140/200: Loss: 0.1362, PA: 0.9464, mIoU: 0.7291
  Batch 150/200: Loss: 0.2864, PA: 0.8878, mIoU: 0.5770
  Batch 160/200: Loss: 0.1782, PA: 0.9379, mIoU: 0.7038
  Batch 170/200: Loss: 0.2949, PA: 0.9043, mIoU: 0.5236
  Batch 180/200: Loss: 0.1032, PA: 0.9631, mIoU: 0.8406
  Batch 190/200: Loss: 0.1793, PA: 0.9329, mIoU: 0.6333
  Batch 200/200: Loss: 0.2153, PA: 0.9359, mIoU: 0.6627

Resumen Epoch 13:
  Tiempo: 399.16s
  Train Loss: 0.2005 | PA: 0.9293 | mIoU: 0.6363
  Val Loss:   0.1710 | PA: 0.9433 | mIoU: 0.6778
  Learning Rate: 0.000296

Epoch 14/80
----------------------------------------
  Batch 10/200: Loss: 0.1781, PA: 0.9351, mIoU: 0.6077
  Batch 20/200: Loss: 0.3547, PA: 0.8856, mIoU: 0.5194
  Batch 30/200: Loss: 0.2251, PA: 0.9175, mIoU: 0.5169
  Batch 40/200: Loss: 0.1562, PA: 0.9336, mIoU: 0.7454
  Batch 50/200: Loss: 0.1916, PA: 0.9063, mIoU: 0.6411
  Batch 60/200: Loss: 0.1822, PA: 0.9213, mIoU: 0.6761
  Batch 70/200: Loss: 0.1528, PA: 0.9586, mIoU: 0.6436
  Batch 80/200: Loss: 0.1415, PA: 0.9413, mIoU: 0.6909
  Batch 90/200: Loss: 0.2133, PA: 0.9179, mIoU: 0.5873
  Batch 100/200: Loss: 0.1486, PA: 0.9443, mIoU: 0.6742
  Batch 110/200: Loss: 0.1996, PA: 0.9510, mIoU: 0.6032
  Batch 120/200: Loss: 0.1911, PA: 0.9378, mIoU: 0.6593
  Batch 130/200: Loss: 0.2022, PA: 0.9347, mIoU: 0.6617
  Batch 140/200: Loss: 0.2545, PA: 0.9224, mIoU: 0.5700
  Batch 150/200: Loss: 0.1745, PA: 0.9356, mIoU: 0.6317
  Batch 160/200: Loss: 0.1778, PA: 0.9250, mIoU: 0.6621
  Batch 170/200: Loss: 0.2922, PA: 0.8819, mIoU: 0.5073
  Batch 180/200: Loss: 0.1715, PA: 0.9422, mIoU: 0.6772
  Batch 190/200: Loss: 0.1946, PA: 0.9080, mIoU: 0.6008
  Batch 200/200: Loss: 0.1673, PA: 0.9462, mIoU: 0.6435

Resumen Epoch 14:
  Tiempo: 405.53s
  Train Loss: 0.1977 | PA: 0.9290 | mIoU: 0.6354
  Val Loss:   0.1636 | PA: 0.9448 | mIoU: 0.6733
  Learning Rate: 0.000295

Epoch 15/80
----------------------------------------
  Batch 10/200: Loss: 0.1636, PA: 0.9468, mIoU: 0.7165
  Batch 20/200: Loss: 0.2272, PA: 0.9193, mIoU: 0.5586
  Batch 30/200: Loss: 0.2127, PA: 0.9416, mIoU: 0.5761
  Batch 40/200: Loss: 0.1514, PA: 0.9441, mIoU: 0.7652
  Batch 50/200: Loss: 0.2050, PA: 0.9394, mIoU: 0.5605
  Batch 60/200: Loss: 0.1560, PA: 0.9514, mIoU: 0.6803
  Batch 70/200: Loss: 0.1469, PA: 0.9590, mIoU: 0.7883
  Batch 80/200: Loss: 0.1968, PA: 0.9353, mIoU: 0.6285
  Batch 90/200: Loss: 0.1778, PA: 0.9299, mIoU: 0.6433
  Batch 100/200: Loss: 0.1675, PA: 0.9429, mIoU: 0.7632
  Batch 110/200: Loss: 0.1455, PA: 0.9432, mIoU: 0.7040
  Batch 120/200: Loss: 0.1884, PA: 0.9433, mIoU: 0.5985
  Batch 130/200: Loss: 0.2126, PA: 0.9319, mIoU: 0.6666
  Batch 140/200: Loss: 0.1971, PA: 0.9278, mIoU: 0.6342
  Batch 150/200: Loss: 0.1235, PA: 0.9565, mIoU: 0.7556
  Batch 160/200: Loss: 0.1559, PA: 0.9364, mIoU: 0.7173
  Batch 170/200: Loss: 0.1973, PA: 0.9420, mIoU: 0.6178
  Batch 180/200: Loss: 0.4343, PA: 0.8703, mIoU: 0.4656
  Batch 190/200: Loss: 0.1890, PA: 0.9202, mIoU: 0.5911
  Batch 200/200: Loss: 0.2045, PA: 0.9252, mIoU: 0.5950

Resumen Epoch 15:
  Tiempo: 403.68s
  Train Loss: 0.1923 | PA: 0.9327 | mIoU: 0.6506
  Val Loss:   0.1663 | PA: 0.9416 | mIoU: 0.6868
  Learning Rate: 0.000293
  ✓ Modelo guardado (mIoU: 0.6868)

Epoch 16/80
----------------------------------------
  Batch 10/200: Loss: 0.1320, PA: 0.9477, mIoU: 0.7824
  Batch 20/200: Loss: 0.2379, PA: 0.9223, mIoU: 0.5615
  Batch 30/200: Loss: 0.1524, PA: 0.9622, mIoU: 0.7832
  Batch 40/200: Loss: 0.2683, PA: 0.9209, mIoU: 0.6466
  Batch 50/200: Loss: 0.1436, PA: 0.9373, mIoU: 0.7080
  Batch 60/200: Loss: 0.1422, PA: 0.9515, mIoU: 0.6836
  Batch 70/200: Loss: 0.2403, PA: 0.9144, mIoU: 0.5726
  Batch 80/200: Loss: 0.1669, PA: 0.9436, mIoU: 0.6885
  Batch 90/200: Loss: 0.1604, PA: 0.9492, mIoU: 0.7248
  Batch 100/200: Loss: 0.1829, PA: 0.9477, mIoU: 0.6056
  Batch 110/200: Loss: 0.2011, PA: 0.9033, mIoU: 0.6410
  Batch 120/200: Loss: 0.1982, PA: 0.9314, mIoU: 0.6554
  Batch 130/200: Loss: 0.1493, PA: 0.9557, mIoU: 0.7049
  Batch 140/200: Loss: 0.3367, PA: 0.8845, mIoU: 0.5417
  Batch 150/200: Loss: 0.2455, PA: 0.9061, mIoU: 0.5866
  Batch 160/200: Loss: 0.1689, PA: 0.9516, mIoU: 0.6725
  Batch 170/200: Loss: 0.1364, PA: 0.9585, mIoU: 0.7333
  Batch 180/200: Loss: 0.2842, PA: 0.9139, mIoU: 0.4580
  Batch 190/200: Loss: 0.2440, PA: 0.9148, mIoU: 0.4886
  Batch 200/200: Loss: 0.1264, PA: 0.9711, mIoU: 0.8547

Resumen Epoch 16:
  Tiempo: 405.53s
  Train Loss: 0.1911 | PA: 0.9335 | mIoU: 0.6526
  Val Loss:   0.1917 | PA: 0.9412 | mIoU: 0.6677
  Learning Rate: 0.000291

Epoch 17/80
----------------------------------------
  Batch 10/200: Loss: 0.2900, PA: 0.8979, mIoU: 0.5262
  Batch 20/200: Loss: 0.1463, PA: 0.9522, mIoU: 0.7477
  Batch 30/200: Loss: 0.1563, PA: 0.9370, mIoU: 0.6549
  Batch 40/200: Loss: 0.2310, PA: 0.8857, mIoU: 0.6229
  Batch 50/200: Loss: 0.2699, PA: 0.8920, mIoU: 0.5005
  Batch 60/200: Loss: 0.2315, PA: 0.9389, mIoU: 0.5305
  Batch 70/200: Loss: 0.1915, PA: 0.9259, mIoU: 0.6391
  Batch 80/200: Loss: 0.1106, PA: 0.9602, mIoU: 0.7986
  Batch 90/200: Loss: 0.1465, PA: 0.9542, mIoU: 0.6422
  Batch 100/200: Loss: 0.2771, PA: 0.9092, mIoU: 0.6208
  Batch 110/200: Loss: 0.1633, PA: 0.9544, mIoU: 0.6891
  Batch 120/200: Loss: 0.1310, PA: 0.9446, mIoU: 0.7412
  Batch 130/200: Loss: 0.3015, PA: 0.8835, mIoU: 0.5483
  Batch 140/200: Loss: 0.1804, PA: 0.9203, mIoU: 0.6124
  Batch 150/200: Loss: 0.2511, PA: 0.9191, mIoU: 0.6007
  Batch 160/200: Loss: 0.1548, PA: 0.9522, mIoU: 0.6768
  Batch 170/200: Loss: 0.2882, PA: 0.8963, mIoU: 0.6583
  Batch 180/200: Loss: 0.1566, PA: 0.9446, mIoU: 0.7333
  Batch 190/200: Loss: 0.1162, PA: 0.9558, mIoU: 0.7742
  Batch 200/200: Loss: 0.1344, PA: 0.9532, mIoU: 0.7161

Resumen Epoch 17:
  Tiempo: 415.12s
  Train Loss: 0.1847 | PA: 0.9361 | mIoU: 0.6612
  Val Loss:   0.1853 | PA: 0.9415 | mIoU: 0.6810
  Learning Rate: 0.000289
  ✓ Mejora significativa: 0.6810 > 0.6677

Epoch 18/80
----------------------------------------
  Batch 10/200: Loss: 0.2449, PA: 0.9289, mIoU: 0.6495
  Batch 20/200: Loss: 0.1357, PA: 0.9413, mIoU: 0.7295
  Batch 30/200: Loss: 0.1587, PA: 0.9591, mIoU: 0.6394
  Batch 40/200: Loss: 0.1939, PA: 0.9295, mIoU: 0.5532
  Batch 50/200: Loss: 0.3292, PA: 0.9207, mIoU: 0.5793
  Batch 60/200: Loss: 0.1598, PA: 0.9482, mIoU: 0.6787
  Batch 70/200: Loss: 0.1336, PA: 0.9578, mIoU: 0.7407
  Batch 80/200: Loss: 0.1149, PA: 0.9575, mIoU: 0.7741
  Batch 90/200: Loss: 0.1678, PA: 0.9310, mIoU: 0.7040
  Batch 100/200: Loss: 0.1650, PA: 0.9386, mIoU: 0.6543
  Batch 110/200: Loss: 0.1874, PA: 0.9356, mIoU: 0.6682
  Batch 120/200: Loss: 0.1560, PA: 0.9447, mIoU: 0.6733
  Batch 130/200: Loss: 0.1287, PA: 0.9596, mIoU: 0.7604
  Batch 140/200: Loss: 0.1721, PA: 0.9412, mIoU: 0.6679
  Batch 150/200: Loss: 0.1407, PA: 0.9390, mIoU: 0.7359
  Batch 160/200: Loss: 0.1476, PA: 0.9449, mIoU: 0.6858
  Batch 170/200: Loss: 0.2131, PA: 0.9296, mIoU: 0.5756
  Batch 180/200: Loss: 0.1785, PA: 0.9352, mIoU: 0.6184
  Batch 190/200: Loss: 0.1686, PA: 0.9420, mIoU: 0.6461
  Batch 200/200: Loss: 0.3727, PA: 0.8686, mIoU: 0.4992

Resumen Epoch 18:
  Tiempo: 405.93s
  Train Loss: 0.1806 | PA: 0.9380 | mIoU: 0.6666
  Val Loss:   0.1730 | PA: 0.9472 | mIoU: 0.6932
  Learning Rate: 0.000286
  ✓ Modelo guardado (mIoU: 0.6932)
  ✓ Mejora significativa: 0.6932 > 0.6810

Epoch 19/80
----------------------------------------
  Batch 10/200: Loss: 0.2039, PA: 0.9155, mIoU: 0.6689
  Batch 20/200: Loss: 0.1199, PA: 0.9569, mIoU: 0.7507
  Batch 30/200: Loss: 0.1012, PA: 0.9689, mIoU: 0.8239
  Batch 40/200: Loss: 0.1030, PA: 0.9591, mIoU: 0.7737
  Batch 50/200: Loss: 0.1341, PA: 0.9683, mIoU: 0.6911
  Batch 60/200: Loss: 0.2874, PA: 0.8864, mIoU: 0.5489
  Batch 70/200: Loss: 0.1582, PA: 0.9436, mIoU: 0.7147
  Batch 80/200: Loss: 0.2656, PA: 0.9026, mIoU: 0.5449
  Batch 90/200: Loss: 0.1565, PA: 0.9395, mIoU: 0.7026
  Batch 100/200: Loss: 0.1890, PA: 0.9442, mIoU: 0.6291
  Batch 110/200: Loss: 0.2100, PA: 0.9176, mIoU: 0.6069
  Batch 120/200: Loss: 0.1472, PA: 0.9520, mIoU: 0.7530
  Batch 130/200: Loss: 0.1864, PA: 0.9270, mIoU: 0.6217
  Batch 140/200: Loss: 0.1527, PA: 0.9598, mIoU: 0.6575
  Batch 150/200: Loss: 0.3849, PA: 0.8769, mIoU: 0.4642
  Batch 160/200: Loss: 0.2086, PA: 0.9233, mIoU: 0.6365
  Batch 170/200: Loss: 0.1031, PA: 0.9688, mIoU: 0.8021
  Batch 180/200: Loss: 0.2029, PA: 0.9268, mIoU: 0.6610
  Batch 190/200: Loss: 0.2077, PA: 0.9318, mIoU: 0.5994
  Batch 200/200: Loss: 0.2934, PA: 0.8958, mIoU: 0.5472

Resumen Epoch 19:
  Tiempo: 405.24s
  Train Loss: 0.1800 | PA: 0.9372 | mIoU: 0.6658
  Val Loss:   0.1649 | PA: 0.9423 | mIoU: 0.6792
  Learning Rate: 0.000283
   EarlyStopping: 1/15 sin mejora

Epoch 20/80
----------------------------------------
  Batch 10/200: Loss: 0.1695, PA: 0.9570, mIoU: 0.6783
  Batch 20/200: Loss: 0.2076, PA: 0.9228, mIoU: 0.5711
  Batch 30/200: Loss: 0.2992, PA: 0.8989, mIoU: 0.5451
  Batch 40/200: Loss: 0.2186, PA: 0.9133, mIoU: 0.6150
  Batch 50/200: Loss: 0.1364, PA: 0.9504, mIoU: 0.7484
  Batch 60/200: Loss: 0.1771, PA: 0.9540, mIoU: 0.5898
  Batch 70/200: Loss: 0.1602, PA: 0.9578, mIoU: 0.6100
  Batch 80/200: Loss: 0.1013, PA: 0.9570, mIoU: 0.7933
  Batch 90/200: Loss: 0.1374, PA: 0.9581, mIoU: 0.7383
  Batch 100/200: Loss: 0.2655, PA: 0.8954, mIoU: 0.5320
  Batch 110/200: Loss: 0.1640, PA: 0.9400, mIoU: 0.7208
  Batch 120/200: Loss: 0.1380, PA: 0.9641, mIoU: 0.7299
  Batch 130/200: Loss: 0.1463, PA: 0.9366, mIoU: 0.6446
  Batch 140/200: Loss: 0.3173, PA: 0.9196, mIoU: 0.6028
  Batch 150/200: Loss: 0.1018, PA: 0.9722, mIoU: 0.7857
  Batch 160/200: Loss: 0.1271, PA: 0.9569, mIoU: 0.7746
  Batch 170/200: Loss: 0.1551, PA: 0.9348, mIoU: 0.7122
  Batch 180/200: Loss: 0.1557, PA: 0.9417, mIoU: 0.7134
  Batch 190/200: Loss: 0.1487, PA: 0.9501, mIoU: 0.7493
  Batch 200/200: Loss: 0.1006, PA: 0.9609, mIoU: 0.8183

Resumen Epoch 20:
  Tiempo: 436.49s
  Train Loss: 0.1708 | PA: 0.9427 | mIoU: 0.6813
  Val Loss:   0.1595 | PA: 0.9472 | mIoU: 0.6978
  Learning Rate: 0.000280
  ✓ Modelo guardado (mIoU: 0.6978)
  ✓ Mejora significativa: 0.6978 > 0.6932

Epoch 21/80
----------------------------------------
  Batch 10/200: Loss: 0.1404, PA: 0.9645, mIoU: 0.7936
  Batch 20/200: Loss: 0.1323, PA: 0.9607, mIoU: 0.7615
  Batch 30/200: Loss: 0.2855, PA: 0.9512, mIoU: 0.7019
  Batch 40/200: Loss: 0.0967, PA: 0.9702, mIoU: 0.8521
  Batch 50/200: Loss: 0.1582, PA: 0.9289, mIoU: 0.6793
  Batch 60/200: Loss: 0.1853, PA: 0.9348, mIoU: 0.5916
  Batch 70/200: Loss: 0.0816, PA: 0.9738, mIoU: 0.8686
  Batch 80/200: Loss: 0.1889, PA: 0.9035, mIoU: 0.6235
  Batch 90/200: Loss: 0.1689, PA: 0.9374, mIoU: 0.6692
  Batch 100/200: Loss: 0.1986, PA: 0.9209, mIoU: 0.6625
  Batch 110/200: Loss: 0.1115, PA: 0.9558, mIoU: 0.7437
  Batch 120/200: Loss: 0.0957, PA: 0.9685, mIoU: 0.8076
  Batch 130/200: Loss: 0.1542, PA: 0.9266, mIoU: 0.6691
  Batch 140/200: Loss: 0.1427, PA: 0.9479, mIoU: 0.6675
  Batch 150/200: Loss: 0.1280, PA: 0.9469, mIoU: 0.7478
  Batch 160/200: Loss: 0.1914, PA: 0.9373, mIoU: 0.6155
  Batch 170/200: Loss: 0.1522, PA: 0.9282, mIoU: 0.6684
  Batch 180/200: Loss: 0.1602, PA: 0.9629, mIoU: 0.6245
  Batch 190/200: Loss: 0.0992, PA: 0.9686, mIoU: 0.7999
  Batch 200/200: Loss: 0.1188, PA: 0.9521, mIoU: 0.7691

Resumen Epoch 21:
  Tiempo: 391.74s
  Train Loss: 0.1756 | PA: 0.9400 | mIoU: 0.6758
  Val Loss:   0.1658 | PA: 0.9436 | mIoU: 0.6761
  Learning Rate: 0.000276
   EarlyStopping: 1/15 sin mejora

Epoch 22/80
----------------------------------------
  Batch 10/200: Loss: 0.2153, PA: 0.9396, mIoU: 0.6302
  Batch 20/200: Loss: 0.1565, PA: 0.9562, mIoU: 0.7484
  Batch 30/200: Loss: 0.1224, PA: 0.9652, mIoU: 0.8042
  Batch 40/200: Loss: 0.2596, PA: 0.9014, mIoU: 0.5692
  Batch 50/200: Loss: 0.2608, PA: 0.9360, mIoU: 0.5164
  Batch 60/200: Loss: 0.1193, PA: 0.9630, mIoU: 0.7519
  Batch 70/200: Loss: 0.1396, PA: 0.9559, mIoU: 0.7327
  Batch 80/200: Loss: 0.1107, PA: 0.9574, mIoU: 0.7894
  Batch 90/200: Loss: 0.1878, PA: 0.9415, mIoU: 0.6597
  Batch 100/200: Loss: 0.1199, PA: 0.9738, mIoU: 0.6985
  Batch 110/200: Loss: 0.1820, PA: 0.9405, mIoU: 0.6963
  Batch 120/200: Loss: 0.1703, PA: 0.9414, mIoU: 0.6756
  Batch 130/200: Loss: 0.1714, PA: 0.9380, mIoU: 0.7574
  Batch 140/200: Loss: 0.1165, PA: 0.9551, mIoU: 0.7891
  Batch 150/200: Loss: 0.2195, PA: 0.9193, mIoU: 0.5753
  Batch 160/200: Loss: 0.1714, PA: 0.9611, mIoU: 0.6441
  Batch 170/200: Loss: 0.0989, PA: 0.9638, mIoU: 0.8153
  Batch 180/200: Loss: 0.0973, PA: 0.9689, mIoU: 0.8256
  Batch 190/200: Loss: 0.2322, PA: 0.9298, mIoU: 0.5974
  Batch 200/200: Loss: 0.1927, PA: 0.9447, mIoU: 0.6077

Resumen Epoch 22:
  Tiempo: 388.83s
  Train Loss: 0.1634 | PA: 0.9443 | mIoU: 0.6886
  Val Loss:   0.1717 | PA: 0.9445 | mIoU: 0.6829
  Learning Rate: 0.000273
   EarlyStopping: 2/15 sin mejora

Epoch 23/80
----------------------------------------
  Batch 10/200: Loss: 0.1746, PA: 0.9446, mIoU: 0.7732
  Batch 20/200: Loss: 0.3269, PA: 0.8827, mIoU: 0.5572
  Batch 30/200: Loss: 0.1471, PA: 0.9360, mIoU: 0.6632
  Batch 40/200: Loss: 0.1844, PA: 0.9440, mIoU: 0.6411
  Batch 50/200: Loss: 0.1968, PA: 0.9312, mIoU: 0.6178
  Batch 60/200: Loss: 0.1846, PA: 0.9298, mIoU: 0.6056
  Batch 70/200: Loss: 0.1448, PA: 0.9659, mIoU: 0.6665
  Batch 80/200: Loss: 0.1791, PA: 0.9352, mIoU: 0.6451
  Batch 90/200: Loss: 0.2211, PA: 0.9210, mIoU: 0.5401
  Batch 100/200: Loss: 0.1695, PA: 0.9585, mIoU: 0.6303
  Batch 110/200: Loss: 0.2264, PA: 0.9249, mIoU: 0.5941
  Batch 120/200: Loss: 0.1791, PA: 0.9451, mIoU: 0.6278
  Batch 130/200: Loss: 0.0937, PA: 0.9636, mIoU: 0.8004
  Batch 140/200: Loss: 0.1462, PA: 0.9382, mIoU: 0.7074
  Batch 150/200: Loss: 0.1530, PA: 0.9493, mIoU: 0.7028
  Batch 160/200: Loss: 0.2804, PA: 0.9505, mIoU: 0.7354
  Batch 170/200: Loss: 0.1838, PA: 0.9403, mIoU: 0.6305
  Batch 180/200: Loss: 0.1383, PA: 0.9733, mIoU: 0.6944
  Batch 190/200: Loss: 0.1159, PA: 0.9659, mIoU: 0.7882
  Batch 200/200: Loss: 0.2549, PA: 0.9063, mIoU: 0.5310

Resumen Epoch 23:
  Tiempo: 396.67s
  Train Loss: 0.1625 | PA: 0.9462 | mIoU: 0.6967
  Val Loss:   0.1614 | PA: 0.9501 | mIoU: 0.7150
  Learning Rate: 0.000269
  ✓ Modelo guardado (mIoU: 0.7150)
  ✓ Mejora significativa: 0.7150 > 0.6978

Epoch 24/80
----------------------------------------
  Batch 10/200: Loss: 0.1685, PA: 0.9309, mIoU: 0.6875
  Batch 20/200: Loss: 0.1316, PA: 0.9471, mIoU: 0.7443
  Batch 30/200: Loss: 0.0954, PA: 0.9604, mIoU: 0.7863
  Batch 40/200: Loss: 0.1056, PA: 0.9620, mIoU: 0.7703
  Batch 50/200: Loss: 0.1436, PA: 0.9385, mIoU: 0.7263
  Batch 60/200: Loss: 0.1827, PA: 0.9540, mIoU: 0.6010
  Batch 70/200: Loss: 0.2135, PA: 0.9340, mIoU: 0.6025
  Batch 80/200: Loss: 0.1078, PA: 0.9697, mIoU: 0.8052
  Batch 90/200: Loss: 0.2306, PA: 0.8900, mIoU: 0.5743
  Batch 100/200: Loss: 0.1405, PA: 0.9596, mIoU: 0.6599
  Batch 110/200: Loss: 0.1505, PA: 0.9590, mIoU: 0.6701
  Batch 120/200: Loss: 0.1070, PA: 0.9608, mIoU: 0.8029
  Batch 130/200: Loss: 0.2266, PA: 0.9323, mIoU: 0.5603
  Batch 140/200: Loss: 0.1174, PA: 0.9520, mIoU: 0.7795
  Batch 150/200: Loss: 0.3464, PA: 0.9144, mIoU: 0.4964
  Batch 160/200: Loss: 0.1493, PA: 0.9569, mIoU: 0.7364
  Batch 170/200: Loss: 0.1224, PA: 0.9573, mIoU: 0.7174
  Batch 180/200: Loss: 0.2104, PA: 0.9334, mIoU: 0.6063
  Batch 190/200: Loss: 0.1020, PA: 0.9666, mIoU: 0.8187
  Batch 200/200: Loss: 0.1511, PA: 0.9491, mIoU: 0.7567

Resumen Epoch 24:
  Tiempo: 390.86s
  Train Loss: 0.1572 | PA: 0.9474 | mIoU: 0.7019
  Val Loss:   0.1573 | PA: 0.9486 | mIoU: 0.6987
  Learning Rate: 0.000265
   EarlyStopping: 1/15 sin mejora

Epoch 25/80
----------------------------------------
  Batch 10/200: Loss: 0.1341, PA: 0.9681, mIoU: 0.6692
  Batch 20/200: Loss: 0.2075, PA: 0.9334, mIoU: 0.5932
  Batch 30/200: Loss: 0.1031, PA: 0.9664, mIoU: 0.7853
  Batch 40/200: Loss: 0.2155, PA: 0.9276, mIoU: 0.6164
  Batch 50/200: Loss: 0.1657, PA: 0.9480, mIoU: 0.6461
  Batch 60/200: Loss: 0.1348, PA: 0.9529, mIoU: 0.7174
  Batch 70/200: Loss: 0.2103, PA: 0.9259, mIoU: 0.6248
  Batch 80/200: Loss: 0.1520, PA: 0.9404, mIoU: 0.6974
  Batch 90/200: Loss: 0.3568, PA: 0.9074, mIoU: 0.6173
  Batch 100/200: Loss: 0.1099, PA: 0.9533, mIoU: 0.7813
  Batch 110/200: Loss: 0.2359, PA: 0.9120, mIoU: 0.5784
  Batch 120/200: Loss: 0.1760, PA: 0.9352, mIoU: 0.6190
  Batch 130/200: Loss: 0.1350, PA: 0.9684, mIoU: 0.7046
  Batch 140/200: Loss: 0.2471, PA: 0.9088, mIoU: 0.5436
  Batch 150/200: Loss: 0.1753, PA: 0.9581, mIoU: 0.6969
  Batch 160/200: Loss: 0.1892, PA: 0.9377, mIoU: 0.5910
  Batch 170/200: Loss: 0.1085, PA: 0.9619, mIoU: 0.8082
  Batch 180/200: Loss: 0.0930, PA: 0.9716, mIoU: 0.8216
  Batch 190/200: Loss: 0.1560, PA: 0.9650, mIoU: 0.7774
  Batch 200/200: Loss: 0.1720, PA: 0.9236, mIoU: 0.6898

Resumen Epoch 25:
  Tiempo: 389.05s
  Train Loss: 0.1574 | PA: 0.9470 | mIoU: 0.7033
  Val Loss:   0.1746 | PA: 0.9438 | mIoU: 0.6679
  Learning Rate: 0.000261
   EarlyStopping: 2/15 sin mejora

Epoch 26/80
----------------------------------------
  Batch 10/200: Loss: 0.1650, PA: 0.9384, mIoU: 0.6878
  Batch 20/200: Loss: 0.1441, PA: 0.9425, mIoU: 0.7517
  Batch 30/200: Loss: 0.1238, PA: 0.9536, mIoU: 0.7618
  Batch 40/200: Loss: 0.1308, PA: 0.9579, mIoU: 0.6882
  Batch 50/200: Loss: 0.1167, PA: 0.9626, mIoU: 0.7903
  Batch 60/200: Loss: 0.2039, PA: 0.9132, mIoU: 0.5907
  Batch 70/200: Loss: 0.1523, PA: 0.9556, mIoU: 0.6455
  Batch 80/200: Loss: 0.1201, PA: 0.9613, mIoU: 0.7540
  Batch 90/200: Loss: 0.1249, PA: 0.9707, mIoU: 0.6802
  Batch 100/200: Loss: 0.1396, PA: 0.9675, mIoU: 0.7853
  Batch 110/200: Loss: 0.1061, PA: 0.9571, mIoU: 0.7994
  Batch 120/200: Loss: 0.3599, PA: 0.9102, mIoU: 0.6731
  Batch 130/200: Loss: 0.0778, PA: 0.9725, mIoU: 0.8533
  Batch 140/200: Loss: 0.2496, PA: 0.8911, mIoU: 0.6194
  Batch 150/200: Loss: 0.1604, PA: 0.9336, mIoU: 0.6898
  Batch 160/200: Loss: 0.1882, PA: 0.9314, mIoU: 0.7026
  Batch 170/200: Loss: 0.0933, PA: 0.9684, mIoU: 0.8179
  Batch 180/200: Loss: 0.1221, PA: 0.9563, mIoU: 0.7814
  Batch 190/200: Loss: 0.1311, PA: 0.9578, mIoU: 0.7647
  Batch 200/200: Loss: 0.1570, PA: 0.9397, mIoU: 0.6561

Resumen Epoch 26:
  Tiempo: 403.62s
  Train Loss: 0.1560 | PA: 0.9490 | mIoU: 0.7092
  Val Loss:   0.1894 | PA: 0.9445 | mIoU: 0.6893
  Learning Rate: 0.000256
   EarlyStopping: 3/15 sin mejora

Epoch 27/80
----------------------------------------
  Batch 10/200: Loss: 0.1413, PA: 0.9533, mIoU: 0.6630
  Batch 20/200: Loss: 0.1430, PA: 0.9456, mIoU: 0.6998
  Batch 30/200: Loss: 0.1561, PA: 0.9347, mIoU: 0.7198
  Batch 40/200: Loss: 0.1592, PA: 0.9605, mIoU: 0.7659
  Batch 50/200: Loss: 0.1864, PA: 0.9419, mIoU: 0.7150
  Batch 60/200: Loss: 0.1701, PA: 0.9588, mIoU: 0.5593
  Batch 70/200: Loss: 0.1259, PA: 0.9719, mIoU: 0.6734
  Batch 80/200: Loss: 0.2439, PA: 0.9447, mIoU: 0.5947
  Batch 90/200: Loss: 0.2367, PA: 0.9209, mIoU: 0.5812
  Batch 100/200: Loss: 0.2912, PA: 0.9117, mIoU: 0.5440
  Batch 110/200: Loss: 0.1189, PA: 0.9645, mIoU: 0.8119
  Batch 120/200: Loss: 0.1050, PA: 0.9680, mIoU: 0.7978
  Batch 130/200: Loss: 0.1247, PA: 0.9518, mIoU: 0.7806
  Batch 140/200: Loss: 0.0919, PA: 0.9619, mIoU: 0.7828
  Batch 150/200: Loss: 0.1394, PA: 0.9749, mIoU: 0.7307
  Batch 160/200: Loss: 0.1166, PA: 0.9674, mIoU: 0.7341
  Batch 170/200: Loss: 0.1673, PA: 0.9547, mIoU: 0.6594
  Batch 180/200: Loss: 0.1343, PA: 0.9516, mIoU: 0.7283
  Batch 190/200: Loss: 0.1154, PA: 0.9744, mIoU: 0.6992
  Batch 200/200: Loss: 0.1748, PA: 0.9374, mIoU: 0.6522

Resumen Epoch 27:
  Tiempo: 392.79s
  Train Loss: 0.1483 | PA: 0.9502 | mIoU: 0.7121
  Val Loss:   0.1731 | PA: 0.9467 | mIoU: 0.6971
  Learning Rate: 0.000251
   EarlyStopping: 4/15 sin mejora

Epoch 28/80
----------------------------------------
  Batch 10/200: Loss: 0.0710, PA: 0.9700, mIoU: 0.8661
  Batch 20/200: Loss: 0.0686, PA: 0.9764, mIoU: 0.8587
  Batch 30/200: Loss: 0.2005, PA: 0.9190, mIoU: 0.6589
  Batch 40/200: Loss: 0.0944, PA: 0.9631, mIoU: 0.8276
  Batch 50/200: Loss: 0.1310, PA: 0.9688, mIoU: 0.7988
  Batch 60/200: Loss: 0.0868, PA: 0.9706, mIoU: 0.8404
  Batch 70/200: Loss: 0.1899, PA: 0.9176, mIoU: 0.6511
  Batch 80/200: Loss: 0.1387, PA: 0.9680, mIoU: 0.8025
  Batch 90/200: Loss: 0.0937, PA: 0.9685, mIoU: 0.8157
  Batch 100/200: Loss: 0.1460, PA: 0.9579, mIoU: 0.6770
  Batch 110/200: Loss: 0.1575, PA: 0.9418, mIoU: 0.5886
  Batch 120/200: Loss: 0.1441, PA: 0.9689, mIoU: 0.7628
  Batch 130/200: Loss: 0.1436, PA: 0.9498, mIoU: 0.7076
  Batch 140/200: Loss: 0.1769, PA: 0.9475, mIoU: 0.6395
  Batch 150/200: Loss: 0.1668, PA: 0.9262, mIoU: 0.6963
  Batch 160/200: Loss: 0.3688, PA: 0.8898, mIoU: 0.4603
  Batch 170/200: Loss: 0.1309, PA: 0.9586, mIoU: 0.7147
  Batch 180/200: Loss: 0.1324, PA: 0.9491, mIoU: 0.7409
  Batch 190/200: Loss: 0.1696, PA: 0.9346, mIoU: 0.7180
  Batch 200/200: Loss: 0.1023, PA: 0.9685, mIoU: 0.8027

Resumen Epoch 28:
  Tiempo: 387.52s
  Train Loss: 0.1503 | PA: 0.9503 | mIoU: 0.7170
  Val Loss:   0.1748 | PA: 0.9478 | mIoU: 0.7005
  Learning Rate: 0.000246
   EarlyStopping: 5/15 sin mejora

Epoch 29/80
----------------------------------------
  Batch 10/200: Loss: 0.1038, PA: 0.9720, mIoU: 0.7676
  Batch 20/200: Loss: 0.1518, PA: 0.9494, mIoU: 0.7296
  Batch 30/200: Loss: 0.1598, PA: 0.9363, mIoU: 0.6672
  Batch 40/200: Loss: 0.1190, PA: 0.9536, mIoU: 0.7898
  Batch 50/200: Loss: 0.2494, PA: 0.9286, mIoU: 0.6609
  Batch 60/200: Loss: 0.0864, PA: 0.9720, mIoU: 0.8109
  Batch 70/200: Loss: 0.2874, PA: 0.9028, mIoU: 0.5951
  Batch 80/200: Loss: 0.0865, PA: 0.9668, mIoU: 0.8185
  Batch 90/200: Loss: 0.1580, PA: 0.9451, mIoU: 0.7161
  Batch 100/200: Loss: 0.0993, PA: 0.9595, mIoU: 0.7960
  Batch 110/200: Loss: 0.1095, PA: 0.9696, mIoU: 0.7710
  Batch 120/200: Loss: 0.1529, PA: 0.9348, mIoU: 0.6695
  Batch 130/200: Loss: 0.2003, PA: 0.9401, mIoU: 0.7207
  Batch 140/200: Loss: 0.1243, PA: 0.9498, mIoU: 0.7169
  Batch 150/200: Loss: 0.1705, PA: 0.9371, mIoU: 0.7188
  Batch 160/200: Loss: 0.2947, PA: 0.8889, mIoU: 0.5356
  Batch 170/200: Loss: 0.1883, PA: 0.9498, mIoU: 0.5824
  Batch 180/200: Loss: 0.1915, PA: 0.9404, mIoU: 0.6659
  Batch 190/200: Loss: 0.1247, PA: 0.9560, mIoU: 0.7465
  Batch 200/200: Loss: 0.0826, PA: 0.9720, mIoU: 0.8442

Resumen Epoch 29:
  Tiempo: 410.24s
  Train Loss: 0.1466 | PA: 0.9527 | mIoU: 0.7203
  Val Loss:   0.1589 | PA: 0.9503 | mIoU: 0.7073
  Learning Rate: 0.000241
   EarlyStopping: 6/15 sin mejora

Epoch 30/80
----------------------------------------
  Batch 10/200: Loss: 0.0683, PA: 0.9767, mIoU: 0.8746
  Batch 20/200: Loss: 0.1271, PA: 0.9669, mIoU: 0.6731
  Batch 30/200: Loss: 0.1299, PA: 0.9470, mIoU: 0.7294
  Batch 40/200: Loss: 0.1217, PA: 0.9432, mIoU: 0.6926
  Batch 50/200: Loss: 0.2202, PA: 0.9329, mIoU: 0.6459
  Batch 60/200: Loss: 0.1825, PA: 0.9410, mIoU: 0.7486
  Batch 70/200: Loss: 0.1390, PA: 0.9501, mIoU: 0.6695
  Batch 80/200: Loss: 0.1530, PA: 0.9509, mIoU: 0.6708
  Batch 90/200: Loss: 0.0848, PA: 0.9707, mIoU: 0.8065
  Batch 100/200: Loss: 0.2002, PA: 0.9572, mIoU: 0.7620
  Batch 110/200: Loss: 0.1892, PA: 0.9487, mIoU: 0.6504
  Batch 120/200: Loss: 0.0608, PA: 0.9797, mIoU: 0.8904
  Batch 130/200: Loss: 0.1463, PA: 0.9331, mIoU: 0.6659
  Batch 140/200: Loss: 0.0820, PA: 0.9671, mIoU: 0.8352
  Batch 150/200: Loss: 0.1026, PA: 0.9788, mIoU: 0.7326
  Batch 160/200: Loss: 0.1879, PA: 0.9566, mIoU: 0.5841
  Batch 170/200: Loss: 0.1606, PA: 0.9464, mIoU: 0.6393
  Batch 180/200: Loss: 0.1011, PA: 0.9665, mIoU: 0.7832
  Batch 190/200: Loss: 0.1018, PA: 0.9633, mIoU: 0.7783
  Batch 200/200: Loss: 0.1070, PA: 0.9591, mIoU: 0.7886

Resumen Epoch 30:
  Tiempo: 402.45s
  Train Loss: 0.1364 | PA: 0.9550 | mIoU: 0.7412
  Val Loss:   0.1567 | PA: 0.9491 | mIoU: 0.7155
  Learning Rate: 0.000236
  ✓ Modelo guardado (mIoU: 0.7155)
   EarlyStopping: 7/15 sin mejora

Epoch 31/80
----------------------------------------
  Batch 10/200: Loss: 0.0802, PA: 0.9739, mIoU: 0.8393
  Batch 20/200: Loss: 0.1262, PA: 0.9716, mIoU: 0.7925
  Batch 30/200: Loss: 0.1170, PA: 0.9747, mIoU: 0.8450
  Batch 40/200: Loss: 0.1338, PA: 0.9548, mIoU: 0.7509
  Batch 50/200: Loss: 0.1889, PA: 0.9574, mIoU: 0.6703
  Batch 60/200: Loss: 0.0973, PA: 0.9546, mIoU: 0.8044
  Batch 70/200: Loss: 0.1501, PA: 0.9412, mIoU: 0.6969
  Batch 80/200: Loss: 0.1192, PA: 0.9623, mIoU: 0.7621
  Batch 90/200: Loss: 0.1123, PA: 0.9627, mIoU: 0.7439
  Batch 100/200: Loss: 0.2023, PA: 0.9219, mIoU: 0.6697
  Batch 110/200: Loss: 0.1317, PA: 0.9547, mIoU: 0.7238
  Batch 120/200: Loss: 0.1386, PA: 0.9642, mIoU: 0.7925
  Batch 130/200: Loss: 0.0964, PA: 0.9641, mIoU: 0.7927
  Batch 140/200: Loss: 0.1351, PA: 0.9514, mIoU: 0.7130
  Batch 150/200: Loss: 0.0987, PA: 0.9666, mIoU: 0.7807
  Batch 160/200: Loss: 0.1960, PA: 0.9488, mIoU: 0.6697
  Batch 170/200: Loss: 0.1205, PA: 0.9529, mIoU: 0.7610
  Batch 180/200: Loss: 0.0713, PA: 0.9742, mIoU: 0.8522
  Batch 190/200: Loss: 0.1608, PA: 0.9415, mIoU: 0.6981
  Batch 200/200: Loss: 0.1530, PA: 0.9366, mIoU: 0.6735

Resumen Epoch 31:
  Tiempo: 389.33s
  Train Loss: 0.1382 | PA: 0.9555 | mIoU: 0.7366
  Val Loss:   0.1756 | PA: 0.9455 | mIoU: 0.6862
  Learning Rate: 0.000231
   EarlyStopping: 8/15 sin mejora

Epoch 32/80
----------------------------------------
  Batch 10/200: Loss: 0.2326, PA: 0.9229, mIoU: 0.5814
  Batch 20/200: Loss: 0.0982, PA: 0.9672, mIoU: 0.8237
  Batch 30/200: Loss: 0.0977, PA: 0.9700, mIoU: 0.8031
  Batch 40/200: Loss: 0.1244, PA: 0.9540, mIoU: 0.7734
  Batch 50/200: Loss: 0.1024, PA: 0.9573, mIoU: 0.7674
  Batch 60/200: Loss: 0.0672, PA: 0.9781, mIoU: 0.8666
  Batch 70/200: Loss: 0.1008, PA: 0.9642, mIoU: 0.8252
  Batch 80/200: Loss: 0.1233, PA: 0.9592, mIoU: 0.7450
  Batch 90/200: Loss: 0.1465, PA: 0.9696, mIoU: 0.6243
  Batch 100/200: Loss: 0.0805, PA: 0.9686, mIoU: 0.8302
  Batch 110/200: Loss: 0.0928, PA: 0.9636, mIoU: 0.8150
  Batch 120/200: Loss: 0.1507, PA: 0.9723, mIoU: 0.6382
  Batch 130/200: Loss: 0.0882, PA: 0.9658, mIoU: 0.8256
  Batch 140/200: Loss: 0.0920, PA: 0.9693, mIoU: 0.8172
  Batch 150/200: Loss: 0.2211, PA: 0.9284, mIoU: 0.7141
  Batch 160/200: Loss: 0.2341, PA: 0.9484, mIoU: 0.5420
  Batch 170/200: Loss: 0.1038, PA: 0.9655, mIoU: 0.7891
  Batch 180/200: Loss: 0.2001, PA: 0.9251, mIoU: 0.6658
  Batch 190/200: Loss: 0.1384, PA: 0.9722, mIoU: 0.6351
  Batch 200/200: Loss: 0.1198, PA: 0.9501, mIoU: 0.7695

Resumen Epoch 32:
  Tiempo: 395.43s
  Train Loss: 0.1346 | PA: 0.9554 | mIoU: 0.7430
  Val Loss:   0.1725 | PA: 0.9469 | mIoU: 0.6939
  Learning Rate: 0.000225
   EarlyStopping: 9/15 sin mejora

Epoch 33/80
----------------------------------------
  Batch 10/200: Loss: 0.1835, PA: 0.9531, mIoU: 0.7166
  Batch 20/200: Loss: 0.1456, PA: 0.9380, mIoU: 0.6438
  Batch 30/200: Loss: 0.1100, PA: 0.9536, mIoU: 0.7795
  Batch 40/200: Loss: 0.0957, PA: 0.9677, mIoU: 0.8103
  Batch 50/200: Loss: 0.1600, PA: 0.9434, mIoU: 0.6726
  Batch 60/200: Loss: 0.1584, PA: 0.9427, mIoU: 0.6229
  Batch 70/200: Loss: 0.1501, PA: 0.9639, mIoU: 0.7848
  Batch 80/200: Loss: 0.1734, PA: 0.9213, mIoU: 0.6809
  Batch 90/200: Loss: 0.0817, PA: 0.9774, mIoU: 0.8433
  Batch 100/200: Loss: 0.0716, PA: 0.9759, mIoU: 0.8379
  Batch 110/200: Loss: 0.1279, PA: 0.9669, mIoU: 0.6988
  Batch 120/200: Loss: 0.1196, PA: 0.9542, mIoU: 0.7681
  Batch 130/200: Loss: 0.0639, PA: 0.9785, mIoU: 0.8829
  Batch 140/200: Loss: 0.1502, PA: 0.9534, mIoU: 0.8029
  Batch 150/200: Loss: 0.1370, PA: 0.9596, mIoU: 0.6482
  Batch 160/200: Loss: 0.0788, PA: 0.9693, mIoU: 0.8450
  Batch 170/200: Loss: 0.0805, PA: 0.9699, mIoU: 0.8277
  Batch 180/200: Loss: 0.1972, PA: 0.9355, mIoU: 0.6046
  Batch 190/200: Loss: 0.0982, PA: 0.9588, mIoU: 0.8194
  Batch 200/200: Loss: 0.1364, PA: 0.9587, mIoU: 0.7063

Resumen Epoch 33:
  Tiempo: 393.91s
  Train Loss: 0.1326 | PA: 0.9557 | mIoU: 0.7430
  Val Loss:   0.1760 | PA: 0.9430 | mIoU: 0.6701
  Learning Rate: 0.000219
   EarlyStopping: 10/15 sin mejora

Epoch 34/80
----------------------------------------
  Batch 10/200: Loss: 0.0697, PA: 0.9731, mIoU: 0.8693
  Batch 20/200: Loss: 0.1439, PA: 0.9704, mIoU: 0.6403
  Batch 30/200: Loss: 0.1287, PA: 0.9682, mIoU: 0.6650
  Batch 40/200: Loss: 0.1252, PA: 0.9643, mIoU: 0.7271
  Batch 50/200: Loss: 0.0843, PA: 0.9708, mIoU: 0.8444
  Batch 60/200: Loss: 0.1329, PA: 0.9637, mIoU: 0.6962
  Batch 70/200: Loss: 0.0790, PA: 0.9698, mIoU: 0.8158
  Batch 80/200: Loss: 0.1117, PA: 0.9629, mIoU: 0.7553
  Batch 90/200: Loss: 0.0975, PA: 0.9681, mIoU: 0.8206
  Batch 100/200: Loss: 0.2939, PA: 0.9038, mIoU: 0.5606
  Batch 110/200: Loss: 0.1156, PA: 0.9525, mIoU: 0.7738
  Batch 120/200: Loss: 0.1492, PA: 0.9693, mIoU: 0.7247
  Batch 130/200: Loss: 0.1206, PA: 0.9499, mIoU: 0.7408
  Batch 140/200: Loss: 0.1430, PA: 0.9529, mIoU: 0.7903
  Batch 150/200: Loss: 0.1435, PA: 0.9547, mIoU: 0.6540
  Batch 160/200: Loss: 0.0772, PA: 0.9727, mIoU: 0.8542
  Batch 170/200: Loss: 0.2347, PA: 0.9161, mIoU: 0.6619
  Batch 180/200: Loss: 0.1467, PA: 0.9448, mIoU: 0.7749
  Batch 190/200: Loss: 0.0871, PA: 0.9697, mIoU: 0.8416
  Batch 200/200: Loss: 0.2055, PA: 0.9272, mIoU: 0.6035

Resumen Epoch 34:
  Tiempo: 394.23s
  Train Loss: 0.1321 | PA: 0.9577 | mIoU: 0.7435
  Val Loss:   0.1754 | PA: 0.9477 | mIoU: 0.7165
  Learning Rate: 0.000213
  ✓ Modelo guardado (mIoU: 0.7165)
  ✓ Mejora significativa: 0.7165 > 0.7150

Epoch 35/80
----------------------------------------
  Batch 10/200: Loss: 0.1230, PA: 0.9538, mIoU: 0.7977
  Batch 20/200: Loss: 0.0905, PA: 0.9785, mIoU: 0.7905
  Batch 30/200: Loss: 0.1082, PA: 0.9719, mIoU: 0.7638
  Batch 40/200: Loss: 0.1498, PA: 0.9626, mIoU: 0.7991
  Batch 50/200: Loss: 0.1063, PA: 0.9633, mIoU: 0.7661
  Batch 60/200: Loss: 0.1148, PA: 0.9507, mIoU: 0.7819
  Batch 70/200: Loss: 0.1484, PA: 0.9557, mIoU: 0.6512
  Batch 80/200: Loss: 0.1165, PA: 0.9591, mIoU: 0.7731
  Batch 90/200: Loss: 0.1545, PA: 0.9543, mIoU: 0.6945
  Batch 100/200: Loss: 0.1119, PA: 0.9636, mIoU: 0.7944
  Batch 110/200: Loss: 0.0861, PA: 0.9615, mIoU: 0.8183
  Batch 120/200: Loss: 0.1172, PA: 0.9649, mIoU: 0.8223
  Batch 130/200: Loss: 0.1236, PA: 0.9584, mIoU: 0.7807
  Batch 140/200: Loss: 0.1721, PA: 0.9496, mIoU: 0.6495
  Batch 150/200: Loss: 0.0866, PA: 0.9732, mIoU: 0.8552
  Batch 160/200: Loss: 0.0971, PA: 0.9623, mIoU: 0.8200
  Batch 170/200: Loss: 0.1134, PA: 0.9638, mIoU: 0.7877
  Batch 180/200: Loss: 0.1314, PA: 0.9699, mIoU: 0.7770
  Batch 190/200: Loss: 0.2627, PA: 0.9220, mIoU: 0.5682
  Batch 200/200: Loss: 0.1376, PA: 0.9576, mIoU: 0.7473

Resumen Epoch 35:
  Tiempo: 416.29s
  Train Loss: 0.1284 | PA: 0.9584 | mIoU: 0.7489
  Val Loss:   0.1632 | PA: 0.9492 | mIoU: 0.7107
  Learning Rate: 0.000207
   EarlyStopping: 1/15 sin mejora

Epoch 36/80
----------------------------------------
  Batch 10/200: Loss: 0.1073, PA: 0.9639, mIoU: 0.8353
  Batch 20/200: Loss: 0.1650, PA: 0.9382, mIoU: 0.6244
  Batch 30/200: Loss: 0.0944, PA: 0.9671, mIoU: 0.7690
  Batch 40/200: Loss: 0.0935, PA: 0.9689, mIoU: 0.8505
  Batch 50/200: Loss: 0.0711, PA: 0.9751, mIoU: 0.8302
  Batch 60/200: Loss: 0.1494, PA: 0.9499, mIoU: 0.6647
  Batch 70/200: Loss: 0.0823, PA: 0.9710, mIoU: 0.8172
  Batch 80/200: Loss: 0.1602, PA: 0.9418, mIoU: 0.7217
  Batch 90/200: Loss: 0.0953, PA: 0.9697, mIoU: 0.8116
  Batch 100/200: Loss: 0.1123, PA: 0.9737, mIoU: 0.8681
  Batch 110/200: Loss: 0.0851, PA: 0.9719, mIoU: 0.7854
  Batch 120/200: Loss: 0.2468, PA: 0.9377, mIoU: 0.5980
  Batch 130/200: Loss: 0.0923, PA: 0.9692, mIoU: 0.8037
  Batch 140/200: Loss: 0.1307, PA: 0.9678, mIoU: 0.6544
  Batch 150/200: Loss: 0.0600, PA: 0.9775, mIoU: 0.8888
  Batch 160/200: Loss: 0.1053, PA: 0.9486, mIoU: 0.7415
  Batch 170/200: Loss: 0.1634, PA: 0.9531, mIoU: 0.6399
  Batch 180/200: Loss: 0.1224, PA: 0.9722, mIoU: 0.8037
  Batch 190/200: Loss: 0.1368, PA: 0.9639, mIoU: 0.7999
  Batch 200/200: Loss: 0.1753, PA: 0.9367, mIoU: 0.6861

Resumen Epoch 36:
  Tiempo: 398.94s
  Train Loss: 0.1228 | PA: 0.9604 | mIoU: 0.7626
  Val Loss:   0.1690 | PA: 0.9486 | mIoU: 0.7085
  Learning Rate: 0.000201
   EarlyStopping: 2/15 sin mejora

Epoch 37/80
----------------------------------------
  Batch 10/200: Loss: 0.1911, PA: 0.9410, mIoU: 0.6017
  Batch 20/200: Loss: 0.1469, PA: 0.9529, mIoU: 0.6604
  Batch 30/200: Loss: 0.1178, PA: 0.9755, mIoU: 0.8302
  Batch 40/200: Loss: 0.1807, PA: 0.9399, mIoU: 0.5978
  Batch 50/200: Loss: 0.1021, PA: 0.9586, mIoU: 0.7773
  Batch 60/200: Loss: 0.1231, PA: 0.9720, mIoU: 0.6851
  Batch 70/200: Loss: 0.1254, PA: 0.9728, mIoU: 0.8208
  Batch 80/200: Loss: 0.1445, PA: 0.9592, mIoU: 0.6636
  Batch 90/200: Loss: 0.1364, PA: 0.9497, mIoU: 0.6409
  Batch 100/200: Loss: 0.0823, PA: 0.9678, mIoU: 0.8339
  Batch 110/200: Loss: 0.2168, PA: 0.9222, mIoU: 0.6485
  Batch 120/200: Loss: 0.1550, PA: 0.9556, mIoU: 0.7441
  Batch 130/200: Loss: 0.1540, PA: 0.9437, mIoU: 0.6678
  Batch 140/200: Loss: 0.1432, PA: 0.9759, mIoU: 0.7521
  Batch 150/200: Loss: 0.0878, PA: 0.9653, mIoU: 0.8255
  Batch 160/200: Loss: 0.1152, PA: 0.9627, mIoU: 0.7668
  Batch 170/200: Loss: 0.1354, PA: 0.9531, mIoU: 0.6896
  Batch 180/200: Loss: 0.1027, PA: 0.9738, mIoU: 0.8137
  Batch 190/200: Loss: 0.0799, PA: 0.9685, mIoU: 0.8241
  Batch 200/200: Loss: 0.1022, PA: 0.9697, mIoU: 0.8133

Resumen Epoch 37:
  Tiempo: 394.48s
  Train Loss: 0.1219 | PA: 0.9605 | mIoU: 0.7549
  Val Loss:   0.1734 | PA: 0.9469 | mIoU: 0.6932
  Learning Rate: 0.000195
   EarlyStopping: 3/15 sin mejora

Epoch 38/80
----------------------------------------
  Batch 10/200: Loss: 0.1522, PA: 0.9419, mIoU: 0.7202
  Batch 20/200: Loss: 0.0946, PA: 0.9671, mIoU: 0.8335
  Batch 30/200: Loss: 0.0973, PA: 0.9704, mIoU: 0.7926
  Batch 40/200: Loss: 0.1879, PA: 0.9332, mIoU: 0.7187
  Batch 50/200: Loss: 0.1663, PA: 0.9354, mIoU: 0.6545
  Batch 60/200: Loss: 0.1161, PA: 0.9543, mIoU: 0.7580
  Batch 70/200: Loss: 0.1704, PA: 0.9543, mIoU: 0.6267
  Batch 80/200: Loss: 0.1036, PA: 0.9547, mIoU: 0.8111
  Batch 90/200: Loss: 0.1144, PA: 0.9769, mIoU: 0.6860
  Batch 100/200: Loss: 0.1380, PA: 0.9387, mIoU: 0.7100
  Batch 110/200: Loss: 0.1311, PA: 0.9722, mIoU: 0.7021
  Batch 120/200: Loss: 0.0825, PA: 0.9689, mIoU: 0.8400
  Batch 130/200: Loss: 0.1006, PA: 0.9745, mIoU: 0.7523
  Batch 140/200: Loss: 0.1010, PA: 0.9628, mIoU: 0.7888
  Batch 150/200: Loss: 0.1307, PA: 0.9602, mIoU: 0.7241
  Batch 160/200: Loss: 0.0912, PA: 0.9598, mIoU: 0.8241
  Batch 170/200: Loss: 0.1735, PA: 0.9338, mIoU: 0.7364
  Batch 180/200: Loss: 0.1169, PA: 0.9522, mIoU: 0.7611
  Batch 190/200: Loss: 0.0894, PA: 0.9669, mIoU: 0.8246
  Batch 200/200: Loss: 0.1025, PA: 0.9783, mIoU: 0.7218

Resumen Epoch 38:
  Tiempo: 413.32s
  Train Loss: 0.1233 | PA: 0.9600 | mIoU: 0.7571
  Val Loss:   0.1707 | PA: 0.9499 | mIoU: 0.7141
  Learning Rate: 0.000189
   EarlyStopping: 4/15 sin mejora

Epoch 39/80
----------------------------------------
  Batch 10/200: Loss: 0.1322, PA: 0.9665, mIoU: 0.8086
  Batch 20/200: Loss: 0.0756, PA: 0.9683, mIoU: 0.8284
  Batch 30/200: Loss: 0.0894, PA: 0.9707, mIoU: 0.7782
  Batch 40/200: Loss: 0.1621, PA: 0.9431, mIoU: 0.6745
  Batch 50/200: Loss: 0.1642, PA: 0.9657, mIoU: 0.7017
  Batch 60/200: Loss: 0.1222, PA: 0.9560, mIoU: 0.7388
  Batch 70/200: Loss: 0.1139, PA: 0.9645, mIoU: 0.7747
  Batch 80/200: Loss: 0.1275, PA: 0.9692, mIoU: 0.6814
  Batch 90/200: Loss: 0.1497, PA: 0.9643, mIoU: 0.7933
  Batch 100/200: Loss: 0.0913, PA: 0.9688, mIoU: 0.8016
  Batch 110/200: Loss: 0.1315, PA: 0.9497, mIoU: 0.7569
  Batch 120/200: Loss: 0.1166, PA: 0.9569, mIoU: 0.7742
  Batch 130/200: Loss: 0.2100, PA: 0.9386, mIoU: 0.6201
  Batch 140/200: Loss: 0.1510, PA: 0.9639, mIoU: 0.6266
  Batch 150/200: Loss: 0.0893, PA: 0.9694, mIoU: 0.8083
  Batch 160/200: Loss: 0.1093, PA: 0.9582, mIoU: 0.8059
  Batch 170/200: Loss: 0.0749, PA: 0.9772, mIoU: 0.8266
  Batch 180/200: Loss: 0.0997, PA: 0.9564, mIoU: 0.8069
  Batch 190/200: Loss: 0.1120, PA: 0.9632, mIoU: 0.7562
  Batch 200/200: Loss: 0.0723, PA: 0.9755, mIoU: 0.8478

Resumen Epoch 39:
  Tiempo: 403.95s
  Train Loss: 0.1166 | PA: 0.9622 | mIoU: 0.7703
  Val Loss:   0.2006 | PA: 0.9489 | mIoU: 0.7060
  Learning Rate: 0.000182
   EarlyStopping: 5/15 sin mejora

Epoch 40/80
----------------------------------------
  Batch 10/200: Loss: 0.1219, PA: 0.9493, mIoU: 0.8039
  Batch 20/200: Loss: 0.1221, PA: 0.9695, mIoU: 0.6883
  Batch 30/200: Loss: 0.1005, PA: 0.9616, mIoU: 0.7798
  Batch 40/200: Loss: 0.1297, PA: 0.9597, mIoU: 0.6863
  Batch 50/200: Loss: 0.1157, PA: 0.9758, mIoU: 0.8319
  Batch 60/200: Loss: 0.0926, PA: 0.9680, mIoU: 0.7966
  Batch 70/200: Loss: 0.1189, PA: 0.9673, mIoU: 0.6998
  Batch 80/200: Loss: 0.1413, PA: 0.9599, mIoU: 0.8368
  Batch 90/200: Loss: 0.1066, PA: 0.9684, mIoU: 0.7816
  Batch 100/200: Loss: 0.1380, PA: 0.9752, mIoU: 0.7299
  Batch 110/200: Loss: 0.1274, PA: 0.9713, mIoU: 0.7974
  Batch 120/200: Loss: 0.1086, PA: 0.9678, mIoU: 0.7818
  Batch 130/200: Loss: 0.1001, PA: 0.9626, mIoU: 0.8031
  Batch 140/200: Loss: 0.0802, PA: 0.9708, mIoU: 0.8225
  Batch 150/200: Loss: 0.0695, PA: 0.9773, mIoU: 0.8468
  Batch 160/200: Loss: 0.0992, PA: 0.9770, mIoU: 0.7687
  Batch 170/200: Loss: 0.0830, PA: 0.9703, mIoU: 0.7983
  Batch 180/200: Loss: 0.1343, PA: 0.9628, mIoU: 0.6528
  Batch 190/200: Loss: 0.0622, PA: 0.9756, mIoU: 0.8771
  Batch 200/200: Loss: 0.1582, PA: 0.9572, mIoU: 0.7398

Resumen Epoch 40:
  Tiempo: 394.61s
  Train Loss: 0.1143 | PA: 0.9632 | mIoU: 0.7725
  Val Loss:   0.1914 | PA: 0.9464 | mIoU: 0.6998
  Learning Rate: 0.000176
   EarlyStopping: 6/15 sin mejora

Epoch 41/80
----------------------------------------
  Batch 10/200: Loss: 0.0900, PA: 0.9670, mIoU: 0.8001
  Batch 20/200: Loss: 0.0633, PA: 0.9771, mIoU: 0.8747
  Batch 30/200: Loss: 0.0851, PA: 0.9707, mIoU: 0.7846
  Batch 40/200: Loss: 0.0683, PA: 0.9763, mIoU: 0.8683
  Batch 50/200: Loss: 0.1215, PA: 0.9446, mIoU: 0.7623
  Batch 60/200: Loss: 0.1426, PA: 0.9688, mIoU: 0.7614
  Batch 70/200: Loss: 0.1683, PA: 0.9698, mIoU: 0.7944
  Batch 80/200: Loss: 0.0939, PA: 0.9631, mIoU: 0.8333
  Batch 90/200: Loss: 0.0936, PA: 0.9604, mIoU: 0.7887
  Batch 100/200: Loss: 0.0611, PA: 0.9771, mIoU: 0.8838
  Batch 110/200: Loss: 0.0998, PA: 0.9626, mIoU: 0.8155
  Batch 120/200: Loss: 0.1084, PA: 0.9615, mIoU: 0.7973
  Batch 130/200: Loss: 0.0545, PA: 0.9788, mIoU: 0.9003
  Batch 140/200: Loss: 0.1408, PA: 0.9499, mIoU: 0.6994
  Batch 150/200: Loss: 0.1141, PA: 0.9666, mIoU: 0.7538
  Batch 160/200: Loss: 0.0804, PA: 0.9713, mIoU: 0.8166
  Batch 170/200: Loss: 0.0706, PA: 0.9726, mIoU: 0.8517
  Batch 180/200: Loss: 0.0786, PA: 0.9745, mIoU: 0.8236
  Batch 190/200: Loss: 0.1002, PA: 0.9560, mIoU: 0.7885
  Batch 200/200: Loss: 0.1475, PA: 0.9455, mIoU: 0.6729

Resumen Epoch 41:
  Tiempo: 393.27s
  Train Loss: 0.1073 | PA: 0.9652 | mIoU: 0.7915
  Val Loss:   0.1959 | PA: 0.9469 | mIoU: 0.6937
  Learning Rate: 0.000170
   EarlyStopping: 7/15 sin mejora

Epoch 42/80
----------------------------------------
  Batch 10/200: Loss: 0.1350, PA: 0.9613, mIoU: 0.6821
  Batch 20/200: Loss: 0.1466, PA: 0.9380, mIoU: 0.6471
  Batch 30/200: Loss: 0.0781, PA: 0.9704, mIoU: 0.8683
  Batch 40/200: Loss: 0.1475, PA: 0.9713, mIoU: 0.7460
  Batch 50/200: Loss: 0.1138, PA: 0.9696, mIoU: 0.7180
  Batch 60/200: Loss: 0.0510, PA: 0.9803, mIoU: 0.9026
  Batch 70/200: Loss: 0.1581, PA: 0.9671, mIoU: 0.5873
  Batch 80/200: Loss: 0.0925, PA: 0.9653, mIoU: 0.7856
  Batch 90/200: Loss: 0.1124, PA: 0.9775, mIoU: 0.8324
  Batch 100/200: Loss: 0.0782, PA: 0.9643, mIoU: 0.8377
  Batch 110/200: Loss: 0.1383, PA: 0.9523, mIoU: 0.7773
  Batch 120/200: Loss: 0.0913, PA: 0.9663, mIoU: 0.7897
  Batch 130/200: Loss: 0.1519, PA: 0.9576, mIoU: 0.6370
  Batch 140/200: Loss: 0.0665, PA: 0.9757, mIoU: 0.8488
  Batch 150/200: Loss: 0.0611, PA: 0.9753, mIoU: 0.8702
  Batch 160/200: Loss: 0.1414, PA: 0.9661, mIoU: 0.6565
  Batch 170/200: Loss: 0.0794, PA: 0.9729, mIoU: 0.8381
  Batch 180/200: Loss: 0.1204, PA: 0.9443, mIoU: 0.7487
  Batch 190/200: Loss: 0.1581, PA: 0.9601, mIoU: 0.7508
  Batch 200/200: Loss: 0.1570, PA: 0.9519, mIoU: 0.6429

Resumen Epoch 42:
  Tiempo: 396.27s
  Train Loss: 0.1062 | PA: 0.9655 | mIoU: 0.7866
  Val Loss:   0.1647 | PA: 0.9523 | mIoU: 0.7260
  Learning Rate: 0.000163
  ✓ Modelo guardado (mIoU: 0.7260)
  ✓ Mejora significativa: 0.7260 > 0.7165

Epoch 43/80
----------------------------------------
  Batch 10/200: Loss: 0.0934, PA: 0.9722, mIoU: 0.8127
  Batch 20/200: Loss: 0.1042, PA: 0.9685, mIoU: 0.7642
  Batch 30/200: Loss: 0.0710, PA: 0.9725, mIoU: 0.8588
  Batch 40/200: Loss: 0.0781, PA: 0.9697, mIoU: 0.8441
  Batch 50/200: Loss: 0.0887, PA: 0.9679, mIoU: 0.8100
  Batch 60/200: Loss: 0.1281, PA: 0.9626, mIoU: 0.6815
  Batch 70/200: Loss: 0.0517, PA: 0.9814, mIoU: 0.8940
  Batch 80/200: Loss: 0.0546, PA: 0.9755, mIoU: 0.8958
  Batch 90/200: Loss: 0.0911, PA: 0.9644, mIoU: 0.7889
  Batch 100/200: Loss: 0.1162, PA: 0.9484, mIoU: 0.7440
  Batch 110/200: Loss: 0.0757, PA: 0.9785, mIoU: 0.8219
  Batch 120/200: Loss: 0.1361, PA: 0.9510, mIoU: 0.6952
  Batch 130/200: Loss: 0.0884, PA: 0.9661, mIoU: 0.8238
  Batch 140/200: Loss: 0.1029, PA: 0.9748, mIoU: 0.8747
  Batch 150/200: Loss: 0.1027, PA: 0.9593, mIoU: 0.7910
  Batch 160/200: Loss: 0.1152, PA: 0.9566, mIoU: 0.7701
  Batch 170/200: Loss: 0.2424, PA: 0.9267, mIoU: 0.7624
  Batch 180/200: Loss: 0.0864, PA: 0.9726, mIoU: 0.7784
  Batch 190/200: Loss: 0.0871, PA: 0.9701, mIoU: 0.8218
  Batch 200/200: Loss: 0.1072, PA: 0.9620, mIoU: 0.7591

Resumen Epoch 43:
  Tiempo: 409.68s
  Train Loss: 0.1097 | PA: 0.9649 | mIoU: 0.7853
  Val Loss:   0.1949 | PA: 0.9449 | mIoU: 0.6974
  Learning Rate: 0.000157
   EarlyStopping: 1/15 sin mejora

Epoch 44/80
----------------------------------------
  Batch 10/200: Loss: 0.1070, PA: 0.9742, mIoU: 0.8741
  Batch 20/200: Loss: 0.1235, PA: 0.9601, mIoU: 0.7724
  Batch 30/200: Loss: 0.0631, PA: 0.9763, mIoU: 0.8671
  Batch 40/200: Loss: 0.1292, PA: 0.9608, mIoU: 0.7031
  Batch 50/200: Loss: 0.1275, PA: 0.9733, mIoU: 0.6448
  Batch 60/200: Loss: 0.0835, PA: 0.9752, mIoU: 0.8062
  Batch 70/200: Loss: 0.0834, PA: 0.9625, mIoU: 0.8327
  Batch 80/200: Loss: 0.1137, PA: 0.9522, mIoU: 0.7893
  Batch 90/200: Loss: 0.0987, PA: 0.9748, mIoU: 0.7537
  Batch 100/200: Loss: 0.0732, PA: 0.9710, mIoU: 0.8414
  Batch 110/200: Loss: 0.1508, PA: 0.9670, mIoU: 0.7701
  Batch 120/200: Loss: 0.0969, PA: 0.9630, mIoU: 0.7810
  Batch 130/200: Loss: 0.0843, PA: 0.9675, mIoU: 0.8303
  Batch 140/200: Loss: 0.0594, PA: 0.9764, mIoU: 0.8879
  Batch 150/200: Loss: 0.0803, PA: 0.9691, mIoU: 0.8067
  Batch 160/200: Loss: 0.1008, PA: 0.9785, mIoU: 0.7467
  Batch 170/200: Loss: 0.0548, PA: 0.9801, mIoU: 0.8749
  Batch 180/200: Loss: 0.1316, PA: 0.9751, mIoU: 0.7722
  Batch 190/200: Loss: 0.1238, PA: 0.9509, mIoU: 0.7671
  Batch 200/200: Loss: 0.1298, PA: 0.9652, mIoU: 0.6802

Resumen Epoch 44:
  Tiempo: 396.61s
  Train Loss: 0.1042 | PA: 0.9666 | mIoU: 0.7921
  Val Loss:   0.2182 | PA: 0.9407 | mIoU: 0.6741
  Learning Rate: 0.000150
   EarlyStopping: 2/15 sin mejora

Epoch 45/80
----------------------------------------
  Batch 10/200: Loss: 0.0799, PA: 0.9669, mIoU: 0.8186
  Batch 20/200: Loss: 0.0811, PA: 0.9717, mIoU: 0.8295
  Batch 30/200: Loss: 0.1176, PA: 0.9752, mIoU: 0.8269
  Batch 40/200: Loss: 0.1096, PA: 0.9621, mIoU: 0.7865
  Batch 50/200: Loss: 0.0915, PA: 0.9685, mIoU: 0.8332
  Batch 60/200: Loss: 0.0848, PA: 0.9649, mIoU: 0.8065
  Batch 70/200: Loss: 0.1601, PA: 0.9498, mIoU: 0.7013
  Batch 80/200: Loss: 0.0621, PA: 0.9730, mIoU: 0.8731
  Batch 90/200: Loss: 0.1390, PA: 0.9752, mIoU: 0.7589
  Batch 100/200: Loss: 0.1005, PA: 0.9629, mIoU: 0.7876
  Batch 110/200: Loss: 0.1064, PA: 0.9664, mIoU: 0.7926
  Batch 120/200: Loss: 0.0657, PA: 0.9779, mIoU: 0.8651
  Batch 130/200: Loss: 0.0784, PA: 0.9719, mIoU: 0.8417
  Batch 140/200: Loss: 0.0831, PA: 0.9705, mIoU: 0.8108
  Batch 150/200: Loss: 0.1174, PA: 0.9543, mIoU: 0.7595
  Batch 160/200: Loss: 0.0619, PA: 0.9721, mIoU: 0.8666
  Batch 170/200: Loss: 0.1092, PA: 0.9706, mIoU: 0.8802
  Batch 180/200: Loss: 0.0885, PA: 0.9711, mIoU: 0.8056
  Batch 190/200: Loss: 0.2620, PA: 0.9291, mIoU: 0.5709
  Batch 200/200: Loss: 0.0822, PA: 0.9648, mIoU: 0.8118

Resumen Epoch 45:
  Tiempo: 368.26s
  Train Loss: 0.1087 | PA: 0.9661 | mIoU: 0.7885
  Val Loss:   0.1875 | PA: 0.9472 | mIoU: 0.6995
  Learning Rate: 0.000143
   EarlyStopping: 3/15 sin mejora

Epoch 46/80
----------------------------------------
  Batch 10/200: Loss: 0.1634, PA: 0.9552, mIoU: 0.7783
  Batch 20/200: Loss: 0.1698, PA: 0.9482, mIoU: 0.7453
  Batch 30/200: Loss: 0.1370, PA: 0.9738, mIoU: 0.6378
  Batch 40/200: Loss: 0.0843, PA: 0.9674, mIoU: 0.8372
  Batch 50/200: Loss: 0.0850, PA: 0.9668, mIoU: 0.8260
  Batch 60/200: Loss: 0.1105, PA: 0.9748, mIoU: 0.8657
  Batch 70/200: Loss: 0.0612, PA: 0.9751, mIoU: 0.8754
  Batch 80/200: Loss: 0.1303, PA: 0.9491, mIoU: 0.7427
  Batch 90/200: Loss: 0.0675, PA: 0.9779, mIoU: 0.8416
  Batch 100/200: Loss: 0.0916, PA: 0.9722, mIoU: 0.7893
  Batch 110/200: Loss: 0.1449, PA: 0.9557, mIoU: 0.7548
  Batch 120/200: Loss: 0.1026, PA: 0.9606, mIoU: 0.7807
  Batch 130/200: Loss: 0.0634, PA: 0.9794, mIoU: 0.8512
  Batch 140/200: Loss: 0.1500, PA: 0.9627, mIoU: 0.7359
  Batch 150/200: Loss: 0.0717, PA: 0.9732, mIoU: 0.8473
  Batch 160/200: Loss: 0.1156, PA: 0.9754, mIoU: 0.8216
  Batch 170/200: Loss: 0.0785, PA: 0.9643, mIoU: 0.8293
  Batch 180/200: Loss: 0.0763, PA: 0.9691, mIoU: 0.8249
  Batch 190/200: Loss: 0.0971, PA: 0.9652, mIoU: 0.7710
  Batch 200/200: Loss: 0.1124, PA: 0.9658, mIoU: 0.7774

Resumen Epoch 46:
  Tiempo: 383.49s
  Train Loss: 0.1050 | PA: 0.9665 | mIoU: 0.7935
  Val Loss:   0.1810 | PA: 0.9479 | mIoU: 0.7057
  Learning Rate: 0.000137
   EarlyStopping: 4/15 sin mejora

Epoch 47/80
----------------------------------------
  Batch 10/200: Loss: 0.0571, PA: 0.9787, mIoU: 0.8834
  Batch 20/200: Loss: 0.0929, PA: 0.9638, mIoU: 0.8020
  Batch 30/200: Loss: 0.1090, PA: 0.9589, mIoU: 0.7567
  Batch 40/200: Loss: 0.0696, PA: 0.9740, mIoU: 0.8222
  Batch 50/200: Loss: 0.0514, PA: 0.9798, mIoU: 0.8886
  Batch 60/200: Loss: 0.1084, PA: 0.9741, mIoU: 0.8623
  Batch 70/200: Loss: 0.0650, PA: 0.9756, mIoU: 0.8532
  Batch 80/200: Loss: 0.0827, PA: 0.9755, mIoU: 0.8048
  Batch 90/200: Loss: 0.1429, PA: 0.9687, mIoU: 0.6127
  Batch 100/200: Loss: 0.0792, PA: 0.9742, mIoU: 0.8276
  Batch 110/200: Loss: 0.0626, PA: 0.9790, mIoU: 0.8603
  Batch 120/200: Loss: 0.0791, PA: 0.9768, mIoU: 0.8268
  Batch 130/200: Loss: 0.1135, PA: 0.9614, mIoU: 0.7577
  Batch 140/200: Loss: 0.1290, PA: 0.9686, mIoU: 0.8120
  Batch 150/200: Loss: 0.0751, PA: 0.9710, mIoU: 0.8373
  Batch 160/200: Loss: 0.1116, PA: 0.9534, mIoU: 0.7941
  Batch 170/200: Loss: 0.1427, PA: 0.9665, mIoU: 0.6555
  Batch 180/200: Loss: 0.0758, PA: 0.9727, mIoU: 0.8467
  Batch 190/200: Loss: 0.0823, PA: 0.9730, mIoU: 0.8159
  Batch 200/200: Loss: 0.1395, PA: 0.9606, mIoU: 0.8116

Resumen Epoch 47:
  Tiempo: 378.08s
  Train Loss: 0.1015 | PA: 0.9685 | mIoU: 0.7989
  Val Loss:   0.1735 | PA: 0.9490 | mIoU: 0.7096
  Learning Rate: 0.000130
   EarlyStopping: 5/15 sin mejora

Epoch 48/80
----------------------------------------
  Batch 10/200: Loss: 0.0580, PA: 0.9749, mIoU: 0.8834
  Batch 20/200: Loss: 0.1246, PA: 0.9677, mIoU: 0.7099
  Batch 30/200: Loss: 0.1230, PA: 0.9749, mIoU: 0.8031
  Batch 40/200: Loss: 0.1123, PA: 0.9598, mIoU: 0.7618
  Batch 50/200: Loss: 0.1232, PA: 0.9705, mIoU: 0.8325
  Batch 60/200: Loss: 0.0946, PA: 0.9632, mIoU: 0.7871
  Batch 70/200: Loss: 0.1064, PA: 0.9669, mIoU: 0.8188
  Batch 80/200: Loss: 0.0904, PA: 0.9723, mIoU: 0.7853
  Batch 90/200: Loss: 0.1185, PA: 0.9661, mIoU: 0.7256
  Batch 100/200: Loss: 0.0912, PA: 0.9656, mIoU: 0.7995
  Batch 110/200: Loss: 0.0797, PA: 0.9815, mIoU: 0.8360
  Batch 120/200: Loss: 0.0619, PA: 0.9752, mIoU: 0.8771
  Batch 130/200: Loss: 0.1108, PA: 0.9591, mIoU: 0.7862
  Batch 140/200: Loss: 0.1145, PA: 0.9618, mIoU: 0.7744
  Batch 150/200: Loss: 0.1080, PA: 0.9758, mIoU: 0.8615
  Batch 160/200: Loss: 0.0920, PA: 0.9696, mIoU: 0.7795
  Batch 170/200: Loss: 0.0590, PA: 0.9772, mIoU: 0.8740
  Batch 180/200: Loss: 0.1617, PA: 0.9545, mIoU: 0.6624
  Batch 190/200: Loss: 0.1139, PA: 0.9795, mIoU: 0.8075
  Batch 200/200: Loss: 0.1164, PA: 0.9475, mIoU: 0.7949

Resumen Epoch 48:
  Tiempo: 376.28s
  Train Loss: 0.1044 | PA: 0.9671 | mIoU: 0.7972
  Val Loss:   0.1772 | PA: 0.9469 | mIoU: 0.6957
  Learning Rate: 0.000124
   EarlyStopping: 6/15 sin mejora

Epoch 49/80
----------------------------------------
  Batch 10/200: Loss: 0.0717, PA: 0.9753, mIoU: 0.8325
  Batch 20/200: Loss: 0.0800, PA: 0.9671, mIoU: 0.8505
  Batch 30/200: Loss: 0.0637, PA: 0.9767, mIoU: 0.8583
  Batch 40/200: Loss: 0.1720, PA: 0.9427, mIoU: 0.6694
  Batch 50/200: Loss: 0.0692, PA: 0.9751, mIoU: 0.8256
  Batch 60/200: Loss: 0.0749, PA: 0.9742, mIoU: 0.8136
  Batch 70/200: Loss: 0.1420, PA: 0.9679, mIoU: 0.7599
  Batch 80/200: Loss: 0.0676, PA: 0.9701, mIoU: 0.8624
  Batch 90/200: Loss: 0.0455, PA: 0.9806, mIoU: 0.8968
  Batch 100/200: Loss: 0.0858, PA: 0.9612, mIoU: 0.8000
  Batch 110/200: Loss: 0.1338, PA: 0.9659, mIoU: 0.7899
  Batch 120/200: Loss: 0.0746, PA: 0.9685, mIoU: 0.8610
  Batch 130/200: Loss: 0.0632, PA: 0.9756, mIoU: 0.8682
  Batch 140/200: Loss: 0.0705, PA: 0.9763, mIoU: 0.8402
  Batch 150/200: Loss: 0.0822, PA: 0.9724, mIoU: 0.8259
  Batch 160/200: Loss: 0.0753, PA: 0.9735, mIoU: 0.8122
  Batch 170/200: Loss: 0.1089, PA: 0.9741, mIoU: 0.8508
  Batch 180/200: Loss: 0.0997, PA: 0.9664, mIoU: 0.7676
  Batch 190/200: Loss: 0.1921, PA: 0.9497, mIoU: 0.6207
  Batch 200/200: Loss: 0.0650, PA: 0.9781, mIoU: 0.8525

Resumen Epoch 49:
  Tiempo: 384.56s
  Train Loss: 0.0977 | PA: 0.9693 | mIoU: 0.8054
  Val Loss:   0.1985 | PA: 0.9470 | mIoU: 0.6982
  Learning Rate: 0.000118
   EarlyStopping: 7/15 sin mejora

Epoch 50/80
----------------------------------------
  Batch 10/200: Loss: 0.0653, PA: 0.9761, mIoU: 0.8571
  Batch 20/200: Loss: 0.1200, PA: 0.9720, mIoU: 0.8499
  Batch 30/200: Loss: 0.1238, PA: 0.9663, mIoU: 0.7178
  Batch 40/200: Loss: 0.0470, PA: 0.9807, mIoU: 0.8949
  Batch 50/200: Loss: 0.1218, PA: 0.9738, mIoU: 0.8195
  Batch 60/200: Loss: 0.0903, PA: 0.9732, mIoU: 0.7820
  Batch 70/200: Loss: 0.1252, PA: 0.9585, mIoU: 0.7650
  Batch 80/200: Loss: 0.1096, PA: 0.9548, mIoU: 0.7575
  Batch 90/200: Loss: 0.1255, PA: 0.9732, mIoU: 0.7878
  Batch 100/200: Loss: 0.2449, PA: 0.9321, mIoU: 0.6547
  Batch 110/200: Loss: 0.0906, PA: 0.9666, mIoU: 0.8432
  Batch 120/200: Loss: 0.0759, PA: 0.9693, mIoU: 0.8509
  Batch 130/200: Loss: 0.0550, PA: 0.9781, mIoU: 0.8897
  Batch 140/200: Loss: 0.0999, PA: 0.9797, mIoU: 0.8806
  Batch 150/200: Loss: 0.0677, PA: 0.9760, mIoU: 0.8371
  Batch 160/200: Loss: 0.0648, PA: 0.9715, mIoU: 0.8623
  Batch 170/200: Loss: 0.0793, PA: 0.9731, mIoU: 0.8327
  Batch 180/200: Loss: 0.0767, PA: 0.9732, mIoU: 0.8247
  Batch 190/200: Loss: 0.0524, PA: 0.9776, mIoU: 0.8871
  Batch 200/200: Loss: 0.0787, PA: 0.9726, mIoU: 0.8237

Resumen Epoch 50:
  Tiempo: 382.30s
  Train Loss: 0.0959 | PA: 0.9695 | mIoU: 0.8124
  Val Loss:   0.1989 | PA: 0.9476 | mIoU: 0.7028
  Learning Rate: 0.000111
   EarlyStopping: 8/15 sin mejora

Epoch 51/80
----------------------------------------
  Batch 10/200: Loss: 0.1348, PA: 0.9646, mIoU: 0.6634
  Batch 20/200: Loss: 0.0594, PA: 0.9798, mIoU: 0.8635
  Batch 30/200: Loss: 0.0637, PA: 0.9757, mIoU: 0.8611
  Batch 40/200: Loss: 0.1174, PA: 0.9622, mIoU: 0.7681
  Batch 50/200: Loss: 0.0715, PA: 0.9754, mIoU: 0.8643
  Batch 60/200: Loss: 0.0706, PA: 0.9764, mIoU: 0.8653
  Batch 70/200: Loss: 0.1023, PA: 0.9778, mIoU: 0.8727
  Batch 80/200: Loss: 0.1328, PA: 0.9660, mIoU: 0.6853
  Batch 90/200: Loss: 0.0504, PA: 0.9798, mIoU: 0.8861
  Batch 100/200: Loss: 0.0780, PA: 0.9688, mIoU: 0.8319
  Batch 110/200: Loss: 0.0801, PA: 0.9669, mIoU: 0.8404
  Batch 120/200: Loss: 0.1870, PA: 0.9674, mIoU: 0.7418
  Batch 130/200: Loss: 0.0795, PA: 0.9776, mIoU: 0.8135
  Batch 140/200: Loss: 0.1080, PA: 0.9667, mIoU: 0.7658
  Batch 150/200: Loss: 0.2125, PA: 0.9350, mIoU: 0.6755
  Batch 160/200: Loss: 0.0684, PA: 0.9719, mIoU: 0.8578
  Batch 170/200: Loss: 0.1413, PA: 0.9610, mIoU: 0.8359
  Batch 180/200: Loss: 0.0596, PA: 0.9776, mIoU: 0.8697
  Batch 190/200: Loss: 0.1294, PA: 0.9782, mIoU: 0.7540
  Batch 200/200: Loss: 0.1051, PA: 0.9638, mIoU: 0.8361

Resumen Epoch 51:
  Tiempo: 414.62s
  Train Loss: 0.0987 | PA: 0.9693 | mIoU: 0.8086
  Val Loss:   0.1861 | PA: 0.9475 | mIoU: 0.7073
  Learning Rate: 0.000105
   EarlyStopping: 9/15 sin mejora

Epoch 52/80
----------------------------------------
  Batch 10/200: Loss: 0.1170, PA: 0.9695, mIoU: 0.8365
  Batch 20/200: Loss: 0.0825, PA: 0.9737, mIoU: 0.8273
  Batch 30/200: Loss: 0.0787, PA: 0.9728, mIoU: 0.8121
  Batch 40/200: Loss: 0.1211, PA: 0.9540, mIoU: 0.7810
  Batch 50/200: Loss: 0.1260, PA: 0.9591, mIoU: 0.7395
  Batch 60/200: Loss: 0.1302, PA: 0.9528, mIoU: 0.6802
  Batch 70/200: Loss: 0.0612, PA: 0.9779, mIoU: 0.8852
  Batch 80/200: Loss: 0.0909, PA: 0.9651, mIoU: 0.8409
  Batch 90/200: Loss: 0.0761, PA: 0.9751, mIoU: 0.8133
  Batch 100/200: Loss: 0.0928, PA: 0.9663, mIoU: 0.7847
  Batch 110/200: Loss: 0.0634, PA: 0.9699, mIoU: 0.8697
  Batch 120/200: Loss: 0.1039, PA: 0.9675, mIoU: 0.7591
  Batch 130/200: Loss: 0.1088, PA: 0.9753, mIoU: 0.8434
  Batch 140/200: Loss: 0.2350, PA: 0.9389, mIoU: 0.5826
  Batch 150/200: Loss: 0.0816, PA: 0.9761, mIoU: 0.8225
  Batch 160/200: Loss: 0.0591, PA: 0.9755, mIoU: 0.8774
  Batch 170/200: Loss: 0.0633, PA: 0.9768, mIoU: 0.8573
  Batch 180/200: Loss: 0.1200, PA: 0.9705, mIoU: 0.6942
  Batch 190/200: Loss: 0.0640, PA: 0.9805, mIoU: 0.8604
  Batch 200/200: Loss: 0.0478, PA: 0.9813, mIoU: 0.9122

Resumen Epoch 52:
  Tiempo: 361.75s
  Train Loss: 0.0916 | PA: 0.9710 | mIoU: 0.8179
  Val Loss:   0.2006 | PA: 0.9447 | mIoU: 0.6935
  Learning Rate: 0.000099
   EarlyStopping: 10/15 sin mejora

Epoch 53/80
----------------------------------------
  Batch 10/200: Loss: 0.0834, PA: 0.9707, mIoU: 0.7869
  Batch 20/200: Loss: 0.1123, PA: 0.9591, mIoU: 0.7475
  Batch 30/200: Loss: 0.0504, PA: 0.9768, mIoU: 0.8918
  Batch 40/200: Loss: 0.1139, PA: 0.9748, mIoU: 0.6911
  Batch 50/200: Loss: 0.0517, PA: 0.9816, mIoU: 0.8782
  Batch 60/200: Loss: 0.0724, PA: 0.9764, mIoU: 0.8355
  Batch 70/200: Loss: 0.0622, PA: 0.9759, mIoU: 0.8645
  Batch 80/200: Loss: 0.0895, PA: 0.9746, mIoU: 0.7995
  Batch 90/200: Loss: 0.1166, PA: 0.9733, mIoU: 0.8125
  Batch 100/200: Loss: 0.0669, PA: 0.9760, mIoU: 0.8465
  Batch 110/200: Loss: 0.0802, PA: 0.9686, mIoU: 0.8536
  Batch 120/200: Loss: 0.0548, PA: 0.9793, mIoU: 0.8445
  Batch 130/200: Loss: 0.1080, PA: 0.9780, mIoU: 0.8709
  Batch 140/200: Loss: 0.0894, PA: 0.9715, mIoU: 0.7994
  Batch 150/200: Loss: 0.0512, PA: 0.9817, mIoU: 0.8979
  Batch 160/200: Loss: 0.0590, PA: 0.9784, mIoU: 0.8691
  Batch 170/200: Loss: 0.0584, PA: 0.9772, mIoU: 0.8681
  Batch 180/200: Loss: 0.1262, PA: 0.9653, mIoU: 0.8315
  Batch 190/200: Loss: 0.1137, PA: 0.9622, mIoU: 0.7358
  Batch 200/200: Loss: 0.0663, PA: 0.9747, mIoU: 0.8583

Resumen Epoch 53:
  Tiempo: 408.47s
  Train Loss: 0.0920 | PA: 0.9708 | mIoU: 0.8198
  Val Loss:   0.1909 | PA: 0.9504 | mIoU: 0.7126
  Learning Rate: 0.000093
   EarlyStopping: 11/15 sin mejora

Epoch 54/80
----------------------------------------
  Batch 10/200: Loss: 0.1061, PA: 0.9764, mIoU: 0.8462
  Batch 20/200: Loss: 0.0989, PA: 0.9661, mIoU: 0.7988
  Batch 30/200: Loss: 0.0876, PA: 0.9643, mIoU: 0.8105
  Batch 40/200: Loss: 0.1155, PA: 0.9513, mIoU: 0.7935
  Batch 50/200: Loss: 0.1089, PA: 0.9668, mIoU: 0.7799
  Batch 60/200: Loss: 0.0536, PA: 0.9828, mIoU: 0.8849
  Batch 70/200: Loss: 0.1000, PA: 0.9796, mIoU: 0.8781
  Batch 80/200: Loss: 0.0889, PA: 0.9703, mIoU: 0.7921
  Batch 90/200: Loss: 0.0575, PA: 0.9768, mIoU: 0.8881
  Batch 100/200: Loss: 0.1285, PA: 0.9504, mIoU: 0.7717
  Batch 110/200: Loss: 0.0559, PA: 0.9783, mIoU: 0.8930
  Batch 120/200: Loss: 0.0709, PA: 0.9748, mIoU: 0.8362
  Batch 130/200: Loss: 0.0481, PA: 0.9838, mIoU: 0.8790
  Batch 140/200: Loss: 0.0742, PA: 0.9707, mIoU: 0.8325
  Batch 150/200: Loss: 0.0880, PA: 0.9692, mIoU: 0.7952
  Batch 160/200: Loss: 0.0608, PA: 0.9758, mIoU: 0.8592
  Batch 170/200: Loss: 0.0659, PA: 0.9748, mIoU: 0.8543
  Batch 180/200: Loss: 0.0993, PA: 0.9530, mIoU: 0.7625
  Batch 190/200: Loss: 0.1416, PA: 0.9611, mIoU: 0.6156
  Batch 200/200: Loss: 0.0513, PA: 0.9800, mIoU: 0.8951

Resumen Epoch 54:
  Tiempo: 401.31s
  Train Loss: 0.0938 | PA: 0.9708 | mIoU: 0.8139
  Val Loss:   0.1755 | PA: 0.9515 | mIoU: 0.7245
  Learning Rate: 0.000087
   EarlyStopping: 12/15 sin mejora

Epoch 55/80
----------------------------------------
  Batch 10/200: Loss: 0.0996, PA: 0.9681, mIoU: 0.7676
  Batch 20/200: Loss: 0.0709, PA: 0.9773, mIoU: 0.8500
  Batch 30/200: Loss: 0.0930, PA: 0.9726, mIoU: 0.7906
  Batch 40/200: Loss: 0.0903, PA: 0.9650, mIoU: 0.8224
  Batch 50/200: Loss: 0.0759, PA: 0.9780, mIoU: 0.8490
  Batch 60/200: Loss: 0.0718, PA: 0.9699, mIoU: 0.8488
  Batch 70/200: Loss: 0.0707, PA: 0.9764, mIoU: 0.8356
  Batch 80/200: Loss: 0.1025, PA: 0.9775, mIoU: 0.7373
  Batch 90/200: Loss: 0.1040, PA: 0.9592, mIoU: 0.7707
  Batch 100/200: Loss: 0.1519, PA: 0.9314, mIoU: 0.6946
  Batch 110/200: Loss: 0.1499, PA: 0.9461, mIoU: 0.6513
  Batch 120/200: Loss: 0.0501, PA: 0.9817, mIoU: 0.8928
  Batch 130/200: Loss: 0.0719, PA: 0.9766, mIoU: 0.8330
  Batch 140/200: Loss: 0.0656, PA: 0.9765, mIoU: 0.8572
  Batch 150/200: Loss: 0.0685, PA: 0.9741, mIoU: 0.8524
  Batch 160/200: Loss: 0.1192, PA: 0.9705, mIoU: 0.8489
  Batch 170/200: Loss: 0.0717, PA: 0.9702, mIoU: 0.8507
  Batch 180/200: Loss: 0.0944, PA: 0.9624, mIoU: 0.7819
  Batch 190/200: Loss: 0.0672, PA: 0.9732, mIoU: 0.8752
  Batch 200/200: Loss: 0.0751, PA: 0.9757, mIoU: 0.8244

Resumen Epoch 55:
  Tiempo: 429.60s
  Train Loss: 0.0938 | PA: 0.9702 | mIoU: 0.8183
  Val Loss:   0.1895 | PA: 0.9499 | mIoU: 0.7103
  Learning Rate: 0.000081
   EarlyStopping: 13/15 sin mejora

Epoch 56/80
----------------------------------------
  Batch 10/200: Loss: 0.0944, PA: 0.9516, mIoU: 0.8415
  Batch 20/200: Loss: 0.1242, PA: 0.9775, mIoU: 0.7767
  Batch 30/200: Loss: 0.0958, PA: 0.9638, mIoU: 0.8101
  Batch 40/200: Loss: 0.0814, PA: 0.9675, mIoU: 0.8162
  Batch 50/200: Loss: 0.0533, PA: 0.9835, mIoU: 0.8808
  Batch 60/200: Loss: 0.0835, PA: 0.9714, mIoU: 0.8305
  Batch 70/200: Loss: 0.0678, PA: 0.9784, mIoU: 0.8728
  Batch 80/200: Loss: 0.0632, PA: 0.9783, mIoU: 0.8447
  Batch 90/200: Loss: 0.0670, PA: 0.9739, mIoU: 0.8582
  Batch 100/200: Loss: 0.0959, PA: 0.9778, mIoU: 0.7522
  Batch 110/200: Loss: 0.0767, PA: 0.9693, mIoU: 0.8462
  Batch 120/200: Loss: 0.0724, PA: 0.9729, mIoU: 0.8570
  Batch 130/200: Loss: 0.0670, PA: 0.9732, mIoU: 0.8553
  Batch 140/200: Loss: 0.0646, PA: 0.9697, mIoU: 0.8966
  Batch 150/200: Loss: 0.0683, PA: 0.9722, mIoU: 0.8625
  Batch 160/200: Loss: 0.0602, PA: 0.9784, mIoU: 0.8682
  Batch 170/200: Loss: 0.0560, PA: 0.9788, mIoU: 0.8813
  Batch 180/200: Loss: 0.0842, PA: 0.9751, mIoU: 0.8303
  Batch 190/200: Loss: 0.1055, PA: 0.9790, mIoU: 0.8512
  Batch 200/200: Loss: 0.0816, PA: 0.9679, mIoU: 0.8255

Resumen Epoch 56:
  Tiempo: 412.13s
  Train Loss: 0.0859 | PA: 0.9725 | mIoU: 0.8263
  Val Loss:   0.1920 | PA: 0.9484 | mIoU: 0.7081
  Learning Rate: 0.000075
   EarlyStopping: 14/15 sin mejora

Epoch 57/80
----------------------------------------
  Batch 10/200: Loss: 0.0848, PA: 0.9725, mIoU: 0.8018
  Batch 20/200: Loss: 0.0763, PA: 0.9746, mIoU: 0.8289
  Batch 30/200: Loss: 0.0881, PA: 0.9644, mIoU: 0.7900
  Batch 40/200: Loss: 0.1133, PA: 0.9736, mIoU: 0.8515
  Batch 50/200: Loss: 0.1407, PA: 0.9624, mIoU: 0.6702
  Batch 60/200: Loss: 0.0579, PA: 0.9796, mIoU: 0.8679
  Batch 70/200: Loss: 0.0853, PA: 0.9649, mIoU: 0.8218
  Batch 80/200: Loss: 0.0863, PA: 0.9685, mIoU: 0.8358
  Batch 90/200: Loss: 0.1733, PA: 0.9349, mIoU: 0.7431
  Batch 100/200: Loss: 0.1618, PA: 0.9537, mIoU: 0.7140
  Batch 110/200: Loss: 0.1136, PA: 0.9761, mIoU: 0.8193
  Batch 120/200: Loss: 0.0616, PA: 0.9774, mIoU: 0.8585
  Batch 130/200: Loss: 0.0603, PA: 0.9787, mIoU: 0.8813
  Batch 140/200: Loss: 0.0520, PA: 0.9786, mIoU: 0.8805
  Batch 150/200: Loss: 0.0747, PA: 0.9718, mIoU: 0.8459
  Batch 160/200: Loss: 0.0764, PA: 0.9690, mIoU: 0.8367
  Batch 170/200: Loss: 0.0848, PA: 0.9659, mIoU: 0.8229
  Batch 180/200: Loss: 0.0706, PA: 0.9739, mIoU: 0.8485
  Batch 190/200: Loss: 0.1065, PA: 0.9769, mIoU: 0.8514
  Batch 200/200: Loss: 0.0705, PA: 0.9714, mIoU: 0.8659

Resumen Epoch 57:
  Tiempo: 406.61s
  Train Loss: 0.0878 | PA: 0.9718 | mIoU: 0.8275
  Val Loss:   0.1880 | PA: 0.9488 | mIoU: 0.7136
  Learning Rate: 0.000069
   EarlyStopping: 15/15 sin mejora

============================================================
EARLY STOPPING ACTIVADO
Sin mejora significativa por 15 épocas
Mejor mIoU alcanzado: 0.7260
Entrenamiento detenido en época 57
============================================================
No description has been provided for this image
No description has been provided for this image
¡Entrenamiento completado exitosamente!
Historial guardado. Mejor mIoU: 0.7260
In [229]:
train_acc_final = history['train_pa'][-1]
val_acc_final   = history['val_pa'][-1]

print(f"Train Accuracy final: {train_acc_final:.4f}")
print(f"Val/Test Accuracy final: {val_acc_final:.4f}")

best_epoch = np.argmax(history['val_miou'])

train_acc_best = history['train_pa'][best_epoch]
val_acc_best   = history['val_pa'][best_epoch]

print(f"Mejor época: {best_epoch + 1}")
print(f"Train Accuracy (mejor época): {train_acc_best:.4f}")
print(f"Val/Test Accuracy (mejor época): {val_acc_best:.4f}")
Train Accuracy final: 0.9718
Val/Test Accuracy final: 0.9488
Mejor época: 42
Train Accuracy (mejor época): 0.9655
Val/Test Accuracy (mejor época): 0.9523
In [230]:
def plot_accuracy(history):
    plt.figure(figsize=(8,5))
    plt.plot(history['train_pa'], label='Train Accuracy')
    plt.plot(history['val_pa'], label='Val/Test Accuracy')
    plt.xlabel('Epoch')
    plt.ylabel('Pixel Accuracy')
    plt.title('Accuracy (Pixel Accuracy)')
    plt.legend()
    plt.grid(alpha=0.3)
    plt.show()

plot_accuracy(history)
No description has been provided for this image
In [231]:
def evaluate_accuracy(model, test_loader, device, n_cls, ignore_index):
    model.eval()
    total_pa = 0
    num_batches = 0

    with torch.no_grad():
        for images, masks in test_loader:
            images = images.to(device)
            masks = masks.to(device)

            outputs = model(images)
            metrics = Metrics(outputs, masks, None,
                              n_cls=n_cls,
                              ignore_index=ignore_index)
            total_pa += metrics.PA()
            num_batches += 1

    return total_pa / num_batches
In [232]:
test_accuracy = evaluate_accuracy(
    model,
    test_dl,
    device="cpu",
    n_cls=n_cls,
    ignore_index=255
)

print(f"Test Accuracy final: {test_accuracy:.4f}")
Test Accuracy final: 0.9508
In [233]:
def plot_accuracy_with_test(history, test_accuracy):
    epochs = range(1, len(history['train_pa']) + 1)

    plt.figure(figsize=(8,5))
    plt.plot(epochs, history['train_pa'], label='Train Accuracy')
    plt.plot(epochs, history['val_pa'], label='Val Accuracy')

    # Línea horizontal del test
    plt.hlines(
        y=test_accuracy,
        xmin=1,
        xmax=len(epochs),
        colors='red',
        linestyles='dashed',
        label=f'Test Accuracy = {test_accuracy:.4f}'
    )

    plt.xlabel('Epoch')
    plt.ylabel('Pixel Accuracy')
    plt.title('Accuracy: Train vs Val vs Test')
    plt.legend()
    plt.grid(alpha=0.3)

plot_accuracy_with_test(history, test_accuracy)
No description has been provided for this image
In [199]:
CLASS_NAMES = {
    0: "background",
    1: "topwear",
    2: "lowerwear",
    3: "dress",
    4: "footwear",
    5: "body",

}
In [200]:
@torch.no_grad()
def predict_mask(model, image, device):
    """
    image: tensor (3,H,W)
    return: tensor (H,W) con clases predichas
    """
    model.eval()
    image = image.unsqueeze(0).to(device)  # (1,3,H,W)
    outputs = model(image)
    pred = torch.argmax(outputs, dim=1)     # (1,H,W)
    return pred.squeeze(0).cpu()


def get_present_classes(mask, n_cls, ignore_index=255):
    """
    Devuelve lista de clases presentes en una máscara
    """
    if torch.is_tensor(mask):
        vals = torch.unique(mask).cpu().numpy()
    else:
        vals = np.unique(mask)

    classes = [int(v) for v in vals if v != ignore_index and v < n_cls]
    return sorted(classes)


def visualize_prediction_sample(
    model,
    dataset,
    n_samples=6,
    device="cpu"
):
    """
    Visualiza:
    Imagen | Máscara GT | Máscara Predicha
    """
    import math
    model.eval()

    # get number of classes / ignore index from dataset if available
    n_cls = dataset.dataset.n_cls if hasattr(dataset, "dataset") else getattr(dataset, "n_cls", n_cls)
    ignore_index = dataset.dataset.ignore_index if hasattr(dataset, "dataset") else getattr(dataset, "ignore_index", 255)


    total_plots = n_samples * 3
    cols = 3
    rows = math.ceil(total_plots / cols)

    fig, axs = plt.subplots(rows, cols, figsize=(18, rows * 3))
    axs = axs.flatten()

    indices = random.sample(range(len(dataset)), n_samples)

    i = 0
    for idx in indices:
        image, gt = dataset[idx]

        # Imagen original
        rgb = to_numpy_image(image.float())

        # GT mask (RGB)
        gt_rgb = mask_to_rgb(
            gt,
            dataset.dataset.n_cls if hasattr(dataset, "dataset") else n_cls,
            dataset.dataset.ignore_index if hasattr(dataset, "dataset") else ignore_index
        )

        # Predicción (mask + RGB)
        pred = predict_mask(model, image, device)
        pred_rgb = mask_to_rgb(
            pred,
            dataset.dataset.n_cls if hasattr(dataset, "dataset") else n_cls,
            dataset.dataset.ignore_index if hasattr(dataset, "dataset") else ignore_index
        )

        # clases presentes en GT y Pred
        gt_classes = get_present_classes(gt, n_cls, ignore_index)
        pred_classes = get_present_classes(pred, n_cls, ignore_index)

        gt_names = [CLASS_NAMES[c] for c in gt_classes]
        pred_names = [CLASS_NAMES[c] for c in pred_classes]

        # mostrar con títulos que incluyen las clases presentes
        plot(axs[i], rgb, "Imagen")
        i += 1
        plot(axs[i], gt_rgb, f"GT\n{gt_names}")
        i += 1
        plot(axs[i], pred_rgb, f"Predicción\n{pred_names}")
        i += 1

        # Títulos con clases
        # gt_title = f"GT\ncls: {gt_classes}"
        # pred_title = f"Predicción\ncls: {pred_classes}"

    for j in range(i, len(axs)):
        axs[j].axis("off")

    plt.suptitle(
        "Visualización de Segmentación: Imagen | GT | Predicción",
        fontsize=16
    )
    plt.tight_layout()
    plt.show()
    
    
    print("=" * 60)
print("VISUALIZANDO PREDICCIONES EN VALIDACIÓN")
print("=" * 60)

visualize_prediction_sample(
    model=model,
    dataset=val_dl.dataset,
    n_samples=6,
    device=device
)
VISUALIZANDO PREDICCIONES EN VALIDACIÓN
============================================================
No description has been provided for this image
============================================================
In [201]:
# Si aún no tienes esta función, aquí está mask_to_rgb mejorada
def mask_to_rgb(mask: torch.Tensor, n_cls: int, ignore_index: int = 255) -> np.ndarray:
    """
    Convierte máscara de índices a RGB usando el colormap adecuado
    
    Args:
        mask: tensor (H,W) con índices de clase
        n_cls: número de clases
        ignore_index: valor a ignorar
    
    Returns:
        np.ndarray (H,W,3) en RGB
    """
    # Paleta de colores (ajusta según tu dataset)
    PALETTE = [
        
        [0, 0, 0],        # 0 background → negro
        [220, 20, 60],    # 1 topwear → rojo (crimson)
        [30, 144, 255],   # 2 lowerwear → azul
        [255, 165, 0],    # 3 dress → naranja
        [138, 43, 226],   # 4 footwear → violeta
        [46, 139, 87],    # 5 body → verde (sea green)
    
    ]

    
    # Asegurar que tenemos colores para todas las clases
    if len(PALETTE) < n_cls:
        PALETTE.extend([[0, 0, 0]] * (n_cls - len(PALETTE)))
    
    if torch.is_tensor(mask):
        mask_np = mask.cpu().numpy().astype(np.uint8)
    else:
        mask_np = mask.astype(np.uint8)
    
    # Crear imagen RGB
    h, w = mask_np.shape
    rgb = np.zeros((h, w, 3), dtype=np.uint8)
    
    for class_idx in range(n_cls):
        rgb[mask_np == class_idx] = PALETTE[class_idx]
    
    # Ignorar índice (si existe)
    if ignore_index < 255:  # Solo si no es 255
        rgb[mask_np == ignore_index] = [255, 255, 255]  # Blanco para ignorar
    
    return rgb

def get_class_names(classes: List[int]) -> List[str]:
    """
    Devuelve nombres de clases basados en índices
    
    Args:
        classes: lista de índices de clase
    
    Returns:
        Lista de nombres de clase
    """
    

    
    return [CLASS_NAMES[c] if c < len(CLASS_NAMES) else f"clase_{c}" 
            for c in classes]

def create_legend(classes: List[int], palette: List[List[int]]) -> List[mpatches.Patch]:
    """
    Crea leyenda para las clases presentes
    
    Args:
        classes: lista de índices de clase
        palette: lista de colores RGB
    
    Returns:
        Lista de patches para la leyenda
    """
    class_names = get_class_names(classes)
    patches = []
    
    for class_idx, class_name in zip(classes, class_names):
        color = [c/255 for c in palette[class_idx]]  # Normalizar a [0,1]
        patch = mpatches.Patch(color=color, label=f"{class_idx}: {class_name}")
        patches.append(patch)
    
    return patches

@torch.no_grad()
def predict_from_image(
    model,
    image_path: str,
    transform,
    device: str = "cpu",
    n_cls: int = 8,
    ignore_index: int = 255,
    show_legend: bool = True,
    show_overlay: bool = True,
    overlay_alpha: float = 0.4,
    figsize: Tuple[int, int] = (18, 8)
) -> Tuple[torch.Tensor, List[int], np.ndarray]:
    """
    Prueba el modelo con una imagen cargada desde disco con visualización mejorada
    
    Args:
        model: modelo entrenado
        image_path: ruta a la imagen (jpg/png)
        transform: mismas transforms usadas en el dataset
        device: cpu / cuda
        n_cls: número de clases
        ignore_index: índice ignorado
        show_legend: mostrar leyenda de clases
        show_overlay: mostrar overlay predicción/imagen
        overlay_alpha: transparencia del overlay
        figsize: tamaño de la figura
    
    Returns:
        Tuple: (máscara predicha, clases presentes, imagen original)
    """
    model.eval()
    
    # -------------------------------------------------
    # 1. Cargar imagen
    # -------------------------------------------------
    img_pil = Image.open(image_path).convert("RGB")
    img_np = np.array(img_pil)
    original_size = img_np.shape[:2]  # Guardar tamaño original
    
    # -------------------------------------------------
    # 2. Transformaciones
    # -------------------------------------------------
    transformed = transform(image=img_np)
    img_t = transformed["image"]  # tensor (3,H,W)
    img_t = img_t.unsqueeze(0).to(device)
    
    # -------------------------------------------------
    # 3. Predicción
    # -------------------------------------------------
    outputs = model(img_t)
    pred = torch.argmax(outputs, dim=1).squeeze(0).cpu()
    
    # Redimensionar a tamaño original si es necesario
    if pred.shape != original_size:
        pred_np = pred.numpy().astype(np.uint8)
        pred_resized = cv2.resize(pred_np, (original_size[1], original_size[0]), 
                                  interpolation=cv2.INTER_NEAREST)
        pred = torch.from_numpy(pred_resized)
    
    # -------------------------------------------------
    # 4. Convertir a RGB
    # -------------------------------------------------
    pred_rgb = mask_to_rgb(pred, n_cls, ignore_index)
    
    # -------------------------------------------------
    # 5. Clases presentes
    # -------------------------------------------------
    present_classes = sorted([
        int(v) for v in torch.unique(pred)
        if v != ignore_index and v < n_cls
    ])
    
    present_names = get_class_names(present_classes)
    
    # -------------------------------------------------
    # 6. Preparar visualización
    # -------------------------------------------------
    n_cols = 3 if show_overlay else 2
    fig, axes = plt.subplots(1, n_cols, figsize=figsize)
    
    if n_cols == 2:
        ax_img, ax_pred = axes
    else:
        ax_img, ax_pred, ax_overlay = axes
    
    # Imagen original
    ax_img.imshow(img_np)
    ax_img.set_title("Imagen Original", fontsize=14, fontweight='bold')
    ax_img.axis('off')
        
    # Predicción
    ax_pred.imshow(pred_rgb)
    pred_title = "Predicción"
    if present_classes:
        pred_title += f"\nClases: {present_classes}\n{', '.join(present_names)}"
    ax_pred.set_title(pred_title, fontsize=14, fontweight='bold')
    ax_pred.axis('off')
    
    # Overlay (opcional)
    if show_overlay:
        # Asegurar mismo tamaño
        if pred_rgb.shape[:2] != img_np.shape[:2]:
            pred_rgb = cv2.resize(pred_rgb, (img_np.shape[1], img_np.shape[0]), 
                                  interpolation=cv2.INTER_NEAREST)
        
        overlay = cv2.addWeighted(
            img_np, 1 - overlay_alpha,
            pred_rgb, overlay_alpha,
            0
        )
        
        ax_overlay.imshow(overlay)
        ax_overlay.set_title(f"Overlay (α={overlay_alpha})", 
                           fontsize=14, fontweight='bold')
        ax_overlay.axis('off')
    
    # -------------------------------------------------
    # 7. Leyenda (opcional)
    # -------------------------------------------------
    if show_legend and present_classes:
        # Paleta (debe coincidir con mask_to_rgb)
        PALETTE = CLASS_COLORS
        
        # Crear leyenda como subplot separado o en la figura
        legend_patches = create_legend(present_classes, PALETTE[:n_cls])
        
        # Agregar leyenda en una posición adecuada
        plt.figlegend(
            handles=legend_patches,
            loc='lower center',
            ncol=min(4, len(present_classes)),
            fontsize=10,
            title="Leyenda de Clases",
            title_fontsize=11,
            bbox_to_anchor=(0.5, -0.05)
        )
    
    plt.suptitle(
        f"Segmentación Semántica - {image_path.split('/')[-1]}",
        fontsize=16,
        fontweight='bold',
        y=1.02
    )
    
    plt.tight_layout()
    plt.show()
    
    # -------------------------------------------------
    # 8. Información por consola
    # -------------------------------------------------
    print("=" * 60)
    print("INFORMACIÓN DE PREDICCIÓN")
    print("=" * 60)
    print(f"Archivo: {image_path}")
    print(f"Tamaño original: {original_size}")
    print(f"Tamaño predicha: {pred.shape}")
    print(f"Clases detectadas: {present_classes}")
    print(f"Nombres: {present_names}")
    print(f"Total píxeles: {pred.numel()}")
    
    # Distribución de clases
    if present_classes:
        print("\nDistribución de píxeles por clase:")
        for class_idx in present_classes:
            count = (pred == class_idx).sum().item()
            percentage = (count / pred.numel()) * 100
            class_name = get_class_names([class_idx])[0]
            print(f"  Clase {class_idx} ({class_name}): {count:,} píxeles ({percentage:.2f}%)")
    
    print("=" * 60)
    
    return pred, present_classes, img_np


# # Versión alternativa más compacta si prefieres menos detalles
# @torch.no_grad()
# def predict_from_image_simple(
#     model,
#     image_path: str,
#     transform,
#     device: str = "cpu",
#     n_cls: int = 8,
#     ignore_index: int = 255
# ) -> Tuple[torch.Tensor, List[int]]:
#     """
#     Versión simplificada similar a visualize_prediction_sample
#     """
#     model.eval()
    
#     # Cargar y transformar
#     img_pil = Image.open(image_path).convert("RGB")
#     img_np = np.array(img_pil)
    
#     transformed = transform(image=img_np)
#     img_t = transformed["image"].unsqueeze(0).to(device)
    
#     # Predecir
#     outputs = model(img_t)
#     pred = torch.argmax(outputs, dim=1).squeeze(0).cpu()
    
#     # Clases presentes
#     present_classes = sorted([
#         int(v) for v in torch.unique(pred)
#         if v != ignore_index and v < n_cls
#     ])
    
#     # Visualización estilo dataset
#     pred_rgb = mask_to_rgb(pred, n_cls, ignore_index)
    
#     fig, axes = plt.subplots(1, 2, figsize=(12, 6))
    
#     axes[0].imshow(img_np)
#     axes[0].set_title("Imagen", fontsize=12)
#     axes[0].axis('off')
    
#     axes[1].imshow(pred_rgb)
#     title = f"Predicción"
#     if present_classes:
#         names = get_class_names(present_classes)
#         title += f"\nClases: {present_classes}\n{', '.join(names)}"
#     axes[1].set_title(title, fontsize=12)
#     axes[1].axis('off')
    
#     plt.tight_layout()
#     plt.show()
    
#     return pred, present_classes
In [234]:
image_path = r"C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\pondoy.png"



#image_path = r"C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\vialpando.png"

pred_mask, classes, original_img = predict_from_image(
    model=model,
    image_path=image_path,
    transform=val_tf,   # ← IMPORTANTE
    device="cpu",
    n_cls=6,
    ignore_index=255,
    show_legend=True,
    show_overlay=True,
    overlay_alpha=0.4
)

# # Versión simplificada (más parecida al dataset)
# pred_mask, classes = predict_from_image_simple(

#     model=model,
#     image_path=image_path,
#     transform=transform,
#     device=device,
#     n_cls=n_cls
# )
No description has been provided for this image
============================================================
INFORMACIÓN DE PREDICCIÓN
============================================================
Archivo: C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\pondoy.png
Tamaño original: (799, 532)
Tamaño predicha: torch.Size([799, 532])
Clases detectadas: [0, 1, 2, 4, 5]
Nombres: ['background', 'topwear', 'lowerwear', 'footwear', 'body']
Total píxeles: 425068

Distribución de píxeles por clase:
  Clase 0 (background): 324,904 píxeles (76.44%)
  Clase 1 (topwear): 31,348 píxeles (7.37%)
  Clase 2 (lowerwear): 44,379 píxeles (10.44%)
  Clase 4 (footwear): 2,849 píxeles (0.67%)
  Clase 5 (body): 21,588 píxeles (5.08%)
============================================================
In [235]:
#image_path = r"C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\pondoy.png"


image_path = r"C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\vialpando.png"

pred_mask, classes, original_img = predict_from_image(
    model=model,
    image_path=image_path,
    transform=val_tf,   # ← IMPORTANTE
    device="cpu",
    n_cls=6,
    ignore_index=255,
    show_legend=True,
    show_overlay=True,
    overlay_alpha=0.4
)

# # Versión simplificada (más parecida al dataset)
# pred_mask, classes = predict_from_image_simple(

#     model=model,
#     image_path=image_path,
#     transform=transform,
#     device=device,
#     n_cls=n_cls
# )
No description has been provided for this image
============================================================
INFORMACIÓN DE PREDICCIÓN
============================================================
Archivo: C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\vialpando.png
Tamaño original: (4480, 3168)
Tamaño predicha: torch.Size([4480, 3168])
Clases detectadas: [0, 1, 2, 4, 5]
Nombres: ['background', 'topwear', 'lowerwear', 'footwear', 'body']
Total píxeles: 14192640

Distribución de píxeles por clase:
  Clase 0 (background): 11,308,681 píxeles (79.68%)
  Clase 1 (topwear): 820,623 píxeles (5.78%)
  Clase 2 (lowerwear): 1,301,940 píxeles (9.17%)
  Clase 4 (footwear): 144,311 píxeles (1.02%)
  Clase 5 (body): 617,085 píxeles (4.35%)
============================================================
In [ ]:
image_path = r"C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\naked.jpeg"

pred_mask, classes, original_img = predict_from_image(
    model=model,
    image_path=image_path,
    transform=val_tf,   # ← IMPORTANTE
    device="cpu",
    n_cls=6,
    ignore_index=255,
    show_legend=True,
    show_overlay=True,
    overlay_alpha=0.4
)
In [203]:
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns


@torch.no_grad()
def compute_confusion_matrix(
    model,
    dataloader,
    n_cls,
    device="cpu",
    ignore_index=255
):
    """
    Calcula la matriz de confusión por píxel para segmentación semántica
    """
    model.eval()

    all_preds = []
    all_gts = []

    for images, gts in dataloader:
        images = images.to(device)
        gts = gts.to(device)

        outputs = model(images)              # (B, C, H, W)
        preds = torch.argmax(outputs, dim=1) # (B, H, W)

        # Aplanar
        preds = preds.view(-1)
        gts   = gts.view(-1)

        # Filtrar ignore_index
        mask = gts != ignore_index
        preds = preds[mask]
        gts   = gts[mask]

        all_preds.append(preds.cpu().numpy())
        all_gts.append(gts.cpu().numpy())

    all_preds = np.concatenate(all_preds)
    all_gts   = np.concatenate(all_gts)

    cm = confusion_matrix(
        all_gts,
        all_preds,
        labels=list(range(n_cls))
    )

    return cm
In [204]:
def plot_confusion_matrix(cm, class_names=None, normalize=True):
    """
    Visualiza la matriz de confusión
    """
    if normalize:
        cm = cm.astype("float") / (cm.sum(axis=1, keepdims=True) + 1e-8)

    plt.figure(figsize=(8, 6))
    sns.heatmap(
        cm,
        annot=True,
        fmt=".2f" if normalize else "d",
        cmap="Blues",
        xticklabels=class_names,
        yticklabels=class_names
    )

    plt.xlabel("Predicción")
    plt.ylabel("Ground Truth")
    plt.title("Matriz de Confusión (Segmentación)")
    plt.tight_layout()
    plt.show()
In [237]:
print("="*60)
print("CALCULANDO MATRIZ DE CONFUSIÓN (VALIDACIÓN)")
print("="*60)

cm = compute_confusion_matrix(
    model=model,
    dataloader=val_dl,
    n_cls=n_cls,
    device=device,
    ignore_index=255
)

class_names = CLASS_NAMES.values()

plot_confusion_matrix(cm, class_names=class_names, normalize=True)
============================================================
CALCULANDO MATRIZ DE CONFUSIÓN (VALIDACIÓN)
============================================================
No description has been provided for this image
In [206]:
def compute_iou_per_class(model, dataloader, n_cls, device="cpu", ignore_index=255):
    model.eval()
    
    intersection = torch.zeros(n_cls)
    union = torch.zeros(n_cls)

    with torch.no_grad():
        for images, gts in dataloader:
            images = images.to(device)
            gts = gts.to(device)

            outputs = model(images)
            preds = torch.argmax(outputs, dim=1)

            mask = gts != ignore_index

            for cls in range(n_cls):
                pred_c = (preds == cls) & mask
                gt_c   = (gts == cls) & mask

                intersection[cls] += (pred_c & gt_c).sum().cpu()
                union[cls] += (pred_c | gt_c).sum().cpu()

    iou_per_class = {}
    for cls in range(n_cls):
        if union[cls] > 0:
            iou_per_class[f"class_{cls}"] = (intersection[cls] / union[cls]).item()
        else:
            iou_per_class[f"class_{cls}"] = 0.0

    return iou_per_class
In [207]:
iou_per_class = compute_iou_per_class(
    model=model,
    dataloader=val_dl,
    n_cls=n_cls,
    device=device,
    ignore_index=255
)

iou_per_class
Out[207]:
{'class_0': 0.9797448515892029,
 'class_1': 0.749193549156189,
 'class_2': 0.6017902493476868,
 'class_3': 0.42478734254837036,
 'class_4': 0.6826684474945068,
 'class_5': 0.7988145351409912}
In [208]:
def plot_iou_per_class(iou_dict):
    plt.figure(figsize=(8, 5))
    plt.bar(iou_dict.keys(), iou_dict.values())
    plt.ylabel("IoU")
    plt.xlabel("Clase")
    plt.title("IoU por Clase")
    plt.ylim(0, 1)
    plt.grid(axis="y", alpha=0.3)
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.show()

plot_iou_per_class(iou_per_class)
No description has been provided for this image
In [209]:
from sklearn.metrics import precision_score, recall_score, f1_score

def compute_precision_recall_f1_per_class(
    model,
    dataloader,
    n_cls,
    device="cpu",
    ignore_index=255
):
    model.eval()

    all_preds = []
    all_gts = []

    with torch.no_grad():
        for images, gts in dataloader:
            images = images.to(device)
            gts = gts.to(device)

            outputs = model(images)
            preds = torch.argmax(outputs, dim=1)

            preds = preds.view(-1)
            gts   = gts.view(-1)

            mask = gts != ignore_index
            all_preds.append(preds[mask].cpu())
            all_gts.append(gts[mask].cpu())

    y_pred = torch.cat(all_preds).numpy()
    y_true = torch.cat(all_gts).numpy()

    precision = precision_score(
        y_true, y_pred,
        labels=list(range(n_cls)),
        average=None,
        zero_division=0
    )
    recall = recall_score(
        y_true, y_pred,
        labels=list(range(n_cls)),
        average=None,
        zero_division=0
    )
    f1 = f1_score(
        y_true, y_pred,
        labels=list(range(n_cls)),
        average=None,
        zero_division=0
    )

    metrics_dict = {
        "precision": {f"class_{i}": precision[i] for i in range(n_cls)},
        "recall":    {f"class_{i}": recall[i] for i in range(n_cls)},
        "f1":        {f"class_{i}": f1[i] for i in range(n_cls)},
    }

    return metrics_dict
In [210]:
metrics_dict = compute_precision_recall_f1_per_class(
    model=model,
    dataloader=val_dl,
    n_cls=n_cls,
    device=device,
    ignore_index=255
)

print(metrics_dict)
{'precision': {'class_0': np.float64(0.9920547748523842), 'class_1': np.float64(0.8438798264754676), 'class_2': np.float64(0.6954297107199435), 'class_3': np.float64(0.7076170681012349), 'class_4': np.float64(0.7288585964419577), 'class_5': np.float64(0.8911843407499725)}, 'recall': {'class_0': np.float64(0.987493335482388), 'class_1': np.float64(0.8697424354030954), 'class_2': np.float64(0.8171612860102935), 'class_3': np.float64(0.5152181460479112), 'class_4': np.float64(0.9150540404930771), 'class_5': np.float64(0.8851494520697565)}, 'f1': {'class_0': np.float64(0.9897687997434291), 'class_1': np.float64(0.856615967000465), 'class_2': np.float64(0.751397090119035), 'class_3': np.float64(0.5962817388975031), 'class_4': np.float64(0.8114117364117364), 'class_5': np.float64(0.8881566449915979)}}
In [211]:
def plot_precision_recall_f1(metrics_dict):
    classes = list(metrics_dict["precision"].keys())

    p = list(metrics_dict["precision"].values())
    r = list(metrics_dict["recall"].values())
    f = list(metrics_dict["f1"].values())

    x = np.arange(len(classes))
    width = 0.25

    plt.figure(figsize=(10, 5))
    plt.bar(x - width, p, width, label="Precision")
    plt.bar(x, r, width, label="Recall")
    plt.bar(x + width, f, width, label="F1-score")

    plt.xticks(x, classes, rotation=45)
    plt.ylim(0, 1)
    plt.ylabel("Score")
    plt.title("Precision / Recall / F1 por Clase")
    plt.legend()
    plt.grid(axis="y", alpha=0.3)
    plt.tight_layout()
    plt.show()
    
plot_precision_recall_f1(metrics_dict)
No description has been provided for this image
In [212]:
def visualize_error_map(image, gt, pred):
    """
    image: (H,W,3)
    gt, pred: (H,W)
    """
    error = (gt != pred)

    plt.figure(figsize=(12, 4))

    plt.subplot(1, 3, 1)
    plt.imshow(image)
    plt.title("Imagen")
    plt.axis("off")

    plt.subplot(1, 3, 2)
    plt.imshow(error, cmap="Reds")
    plt.title("Mapa de Error")
    plt.axis("off")

    plt.subplot(1, 3, 3)
    overlay = image.copy()
    overlay[error] = [255, 0, 0]
    plt.imshow(overlay)
    plt.title("Errores Superpuestos")
    plt.axis("off")

    plt.tight_layout()
    plt.show()
In [213]:
def run_error_map_from_loader(
    model,
    dataloader,
    device="cpu",
    ignore_index=255
):
    model.eval()

    images, gts = next(iter(dataloader))
    images = images.to(device)
    gts = gts.to(device)

    with torch.no_grad():
        outputs = model(images)
        preds = torch.argmax(outputs, dim=1)

    # TOMAMOS SOLO LA PRIMERA IMAGEN DEL BATCH
    img = images[0].cpu()
    gt  = gts[0].cpu()
    pred = preds[0].cpu()

    # ---- Desnormalizar imagen (ImageNet) ----
    img = img.permute(1, 2, 0).numpy()
    img = (img - img.min()) / (img.max() - img.min())
    img = (img * 255).astype(np.uint8)

    gt = gt.numpy()
    pred = pred.numpy()

    # Ignorar ignore_index en visualización
    mask = gt != ignore_index
    gt = np.where(mask, gt, 0)
    pred = np.where(mask, pred, 0)

    visualize_error_map(img, gt, pred)
In [214]:
run_error_map_from_loader(
    model=model,
    dataloader=val_dl,
    device=device,
    ignore_index=255
)
No description has been provided for this image

SEPARACION DE PRENDAS Y COLOR¶

In [215]:
import cv2
import numpy as np

image_path = r"C:\Users\pc\Desktop\Proyecto_Segmentacion\Imagen_eval\vialpando.png"

# Cargar imagen
img_bgr = cv2.imread(image_path)

# Validar
assert img_bgr is not None, "Imagen no encontrada"

# Convertir BGR → RGB
img_np = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

print(img_np.shape, img_np.dtype)
(4480, 3168, 3) uint8
In [216]:
@torch.no_grad()
def predict_mask(model, image_tensor, device):
    model.eval()
    image_tensor = image_tensor.unsqueeze(0).to(device)
    outputs = model(image_tensor)
    pred = torch.argmax(outputs, dim=1)
    return pred.squeeze(0).cpu().numpy()


pred_mask  # (H, W) con clases
Out[216]:
tensor([[0, 0, 0,  ..., 0, 0, 0],
        [0, 0, 0,  ..., 0, 0, 0],
        [0, 0, 0,  ..., 0, 0, 0],
        ...,
        [0, 0, 0,  ..., 0, 0, 0],
        [0, 0, 0,  ..., 0, 0, 0],
        [0, 0, 0,  ..., 0, 0, 0]], dtype=torch.uint8)
In [217]:
def get_garment_mask(pred_mask, class_id):
    """
    pred_mask: (H,W)
    class_id: int (ej: camiseta)
    return: mask (H,W) uint8 para LaMa
    """
    # 1. Zona NO prenda = 255 (a rellenar)
    mask = np.ones_like(pred_mask, dtype=np.uint8) * 255

    # 2. Zona prenda = 0 (se conserva)
    mask[pred_mask == class_id] = 0

    return mask
In [218]:
def clean_mask(mask):
    kernel = np.ones((7, 7), np.uint8)

    # Eliminar agujeros
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

    # Suavizar bordes
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)

    return mask
In [219]:
mask = get_garment_mask(pred_mask, class_id=1)  # camiseta
mask = clean_mask(mask)
In [220]:
def show_mask(image, mask):
    plt.figure(figsize=(10,4))

    plt.subplot(1,2,1)
    plt.imshow(image)
    plt.title("Imagen")
    plt.axis("off")

    plt.subplot(1,2,2)
    plt.imshow(mask, cmap="gray")
    plt.title("Máscara para LaMa (blanco = rellenar)")
    plt.axis("off")

    plt.show()
In [221]:
show_mask(img_np, mask)

cv2.imwrite("input_image.png", cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR))
cv2.imwrite("input_mask.png", mask)
No description has been provided for this image
Out[221]:
True
In [222]:
%pip install opencv-python matplotlib
Requirement already satisfied: opencv-python in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (4.12.0.88)
Requirement already satisfied: matplotlib in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (3.10.8)
Requirement already satisfied: numpy<2.3.0,>=2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from opencv-python) (2.2.6)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (1.3.3)
Requirement already satisfied: cycler>=0.10 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (4.61.0)
Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (1.4.9)
Requirement already satisfied: packaging>=20.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (25.0)
Requirement already satisfied: pillow>=8 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (12.0.0)
Requirement already satisfied: pyparsing>=3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (3.2.5)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: six>=1.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)
Note: you may need to restart the kernel to use updated packages.
In [223]:
%pip install numpy cython
%pip install scikit-image
Requirement already satisfied: numpy in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.2.6)
Requirement already satisfied: cython in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (3.2.3)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: scikit-image in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (0.25.2)
Requirement already satisfied: numpy>=1.24 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (2.2.6)
Requirement already satisfied: scipy>=1.11.4 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (1.16.3)
Requirement already satisfied: networkx>=3.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (3.6.1)
Requirement already satisfied: pillow>=10.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (12.0.0)
Requirement already satisfied: imageio!=2.35.0,>=2.33 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (2.37.2)
Requirement already satisfied: tifffile>=2022.8.12 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (2025.12.12)
Requirement already satisfied: packaging>=21 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (25.0)
Requirement already satisfied: lazy-loader>=0.4 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from scikit-image) (0.4)
Note: you may need to restart the kernel to use updated packages.
In [224]:
%pip install torch torchvision
%pip install albumentations
%pip install omegaconf
%pip install tqdm
Requirement already satisfied: torch in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.9.1)
Requirement already satisfied: torchvision in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (0.24.1)
Requirement already satisfied: filelock in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (3.20.0)
Requirement already satisfied: typing-extensions>=4.10.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (4.15.0)
Requirement already satisfied: sympy>=1.13.3 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (1.14.0)
Requirement already satisfied: networkx>=2.5.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (3.6.1)
Requirement already satisfied: jinja2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (3.1.6)
Requirement already satisfied: fsspec>=0.8.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (2025.12.0)
Requirement already satisfied: setuptools in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torch) (80.9.0)
Requirement already satisfied: numpy in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torchvision) (2.2.6)
Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from torchvision) (12.0.0)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from sympy>=1.13.3->torch) (1.3.0)
Requirement already satisfied: MarkupSafe>=2.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from jinja2->torch) (3.0.3)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: albumentations in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.0.8)
Requirement already satisfied: numpy>=1.24.4 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (2.2.6)
Requirement already satisfied: scipy>=1.10.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (1.16.3)
Requirement already satisfied: PyYAML in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (6.0.3)
Requirement already satisfied: pydantic>=2.9.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (2.12.5)
Requirement already satisfied: albucore==0.0.24 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (0.0.24)
Requirement already satisfied: opencv-python-headless>=4.9.0.80 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albumentations) (4.12.0.88)
Requirement already satisfied: stringzilla>=3.10.4 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albucore==0.0.24->albumentations) (4.4.0)
Requirement already satisfied: simsimd>=5.9.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from albucore==0.0.24->albumentations) (6.5.3)
Requirement already satisfied: annotated-types>=0.6.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (0.7.0)
Requirement already satisfied: pydantic-core==2.41.5 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (2.41.5)
Requirement already satisfied: typing-extensions>=4.14.1 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (4.15.0)
Requirement already satisfied: typing-inspection>=0.4.2 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from pydantic>=2.9.2->albumentations) (0.4.2)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: omegaconf in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (2.3.0)
Requirement already satisfied: antlr4-python3-runtime==4.9.* in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from omegaconf) (4.9.3)
Requirement already satisfied: PyYAML>=5.1.0 in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from omegaconf) (6.0.3)
Note: you may need to restart the kernel to use updated packages.
Requirement already satisfied: tqdm in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (4.67.1)
Requirement already satisfied: colorama in c:\users\pc\desktop\proyecto_segmentacion\.venv\lib\site-packages (from tqdm) (0.4.6)
Note: you may need to restart the kernel to use updated packages.
In [225]:
!mkdir -p pretrained
A subdirectory or file -p already exists.
Error occurred while processing: -p.
A subdirectory or file pretrained already exists.
Error occurred while processing: pretrained.
In [226]:
# Imagen inpainted (sin camiseta)
inpainted = cv2.imread(r"C:\Users\pc\Desktop\Proyecto_Segmentacion\input_mask.png")
inpainted = cv2.cvtColor(inpainted, cv2.COLOR_BGR2RGB)
In [227]:
import matplotlib.pyplot as plt

plt.figure(figsize=(12,4))

plt.subplot(1,3,1)
plt.imshow(img_np)
plt.title("Original")
plt.axis("off")

plt.subplot(1,3,2)
plt.imshow(mask, cmap="gray")
plt.title("Máscara (prenda)")
plt.axis("off")

plt.subplot(1,3,3)
plt.imshow(inpainted)
plt.title("Rellenado con LaMa")
plt.axis("off")

plt.tight_layout()
plt.show()
No description has been provided for this image